Loading [MathJax]/extensions/tex2jax.js

2014年8月14日木曜日

Javaだけで作るWebSocketサーバ

JavaだけでWebSocketを動かすサーバを作りました。 動作としてはブラウザでHTMLを表示した後にWebSocket経由でtestという文字を送受信するだけです。 後は双方向にメッセージをやり取りできるようにするのと、処理を汎用化すれば実用的に使えるようになるはず。 ほとんどはWebSocket サーバの実装とプロトコル解説を参考にさせてもらいました。 引っかかったところとしては、WebSocketのレスポンスヘッダの改行コードがCRLFじゃないといけないところ、慣れていないということもありましたがbyteを使ってバイナリ処理を書くところでした。Java(SE)だけで作ろうとしていたため暗号化部分の処理ができるか心配でしたが幸運なことにJava8でBase64の処理クラスが追加されていたのでBase64の処理を素で書かずにすんでよかったです。
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
<script type="text/javascript">
var ws = new WebSocket("ws://localhost:5000");
ws.onopen = function(){
ws.send("test");
console.log("open");
}
ws.onerror = function(){
console.log("error");
}
ws.onclose = function(){
console.log("close");
}
ws.onmessage = function(msg) {
console.log("msg received:" + msg.data);
var log = document.getElementById("log");
log.innerHTML += "msg received:" + msg.data;
};
</script>
</head>
<body>
<h1>WebSocket Test</h1>
<div id="log"></div>
</body>
</html>
view raw index.html hosted with ❤ by GitHub
package com.sanofc.ws;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashSet;
public class WebSocketServer {
private static HashSet<Socket> socketSet = new HashSet<Socket>();
public static void main(String[] args) throws IOException {
int port = 5000;
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket socket = serverSocket.accept();
System.out.println("accept");
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
boolean isWebSocket = false;
String webSocketKey = "";
while (!(line = in.readLine()).equals("")) {
System.out.println(line);
String[] spLine = line.split(":");
if (isWebSocket && spLine[0].equals("Sec-WebSocket-Key")) {
webSocketKey = spLine[1].trim();
}
if (spLine[0].equals("Upgrade") && spLine[1].trim().equals("websocket")) {
isWebSocket = true;
}
}
if (isWebSocket) {
socketSet.add(socket);
WebSocketThread webSocketThread = new WebSocketThread(socket, webSocketKey);
webSocketThread.start();
} else {
HttpResponseThread httpRequest = new HttpResponseThread(socket);
httpRequest.start();
}
}
}
}
class HttpResponseThread extends Thread {
Socket socket;
public HttpResponseThread(Socket socket) {
this.socket = socket;
}
public void run() {
PrintStream client = null;
try {
Path indexPath = Paths.get("index.html");
byte[] indexBytes = Files.readAllBytes(indexPath);
client = new PrintStream(socket.getOutputStream());
client.println("HTTP/1.1 200 OK");
client.println("Content_type:text/html");
client.println();
client.write(indexBytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
client.flush();
client.close();
}
}
}
class WebSocketThread extends Thread {
Socket socket;
String webSocketKey;
WebSocketThread(Socket socket, String webSocketKey) {
System.out.println("Start WebSocket Thread");
this.socket = socket;
this.webSocketKey = webSocketKey;
}
public void run() {
OutputStreamWriter os = null;
try {
//Create Sec-WebSocket-Accept field.
webSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] cryptedKey = md.digest(webSocketKey.getBytes());
byte[] encodedKey = Base64.getEncoder().encode(cryptedKey);
System.out.println(new String(encodedKey));
//Response HTTP Upgrade Response Header.
StringBuffer header = new StringBuffer();
header.append("HTTP/1.1 101 Switching Protocols\r\n");
header.append("Upgrade: websocket\r\n");
header.append("Connection: Upgrade\r\n");
header.append("Sec-WebSocket-Accept: " + new String(encodedKey) + "\r\n");
header.append("\r\n");
os = new OutputStreamWriter(socket.getOutputStream());
os.write(header.toString());
System.out.println(header.toString());
os.flush();
//Parse received data.
byte[] b = new byte[1024];
socket.getInputStream().read(b);
ByteBuffer buf = ByteBuffer.wrap(b);
byte firstByte = buf.get();
int fin = (firstByte & 0x80) >>> 7;
int opcode = firstByte & 0x0F;
byte secondByte = buf.get();
int mask = (secondByte & 0x80) >>> 7;
int payloadLength = secondByte & 0x7F;
int maskingKey = buf.getInt();
int applicationData = buf.getInt();
int unmaskedData = applicationData ^ maskingKey;
byte[] unmaskedByteData = new byte[4];
unmaskedByteData[3] = (byte)(unmaskedData & 0xff);
unmaskedByteData[2] = (byte)(unmaskedData >> 8 & 0xff);
unmaskedByteData[1] = (byte)(unmaskedData >> 16 & 0xff);
unmaskedByteData[0] = (byte)(unmaskedData >> 24 & 0xff);
//Print received data.
System.out.println("fin:" + fin);
System.out.println("opcode:" + opcode);
System.out.println("mask:" + mask);
System.out.println("payloadLength:" + payloadLength);
System.out.println("maskingKey:" + maskingKey);
System.out.println("applicationData:" + new String(unmaskedByteData));
//Send data.
ByteBuffer ob = ByteBuffer.allocate(6);
ob.put((byte)0x81);
ob.put((byte)0x4);
ob.put("test".getBytes());
socket.getOutputStream().write(ob.array());
socket.close();
} catch (IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}

0 件のコメント: