Skip to content

Commit

Permalink
websocket protocol support (#73)
Browse files Browse the repository at this point in the history
* websocket protocol support

* echo server client

* added copyright header to all new files

---------

Co-authored-by: Vadim Yelisseyev <[email protected]>
  • Loading branch information
twister55 and Vadim Yelisseyev authored Nov 4, 2024
1 parent 267418a commit b5bc00d
Show file tree
Hide file tree
Showing 30 changed files with 2,026 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/one/nio/http/Response.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public class Response {
public static final String UNSUPPORTED_MEDIA_TYPE = "415 Unsupported Media Type";
public static final String REQUESTED_RANGE_NOT_SATISFIABLE = "416 Requested Range Not Satisfiable";
public static final String EXPECTATION_FAILED = "417 Expectation Failed";
public static final String UPGRADE_REQUIRED = "426 Upgrade Required";
public static final String INTERNAL_ERROR = "500 Internal Server Error";
public static final String NOT_IMPLEMENTED = "501 Not Implemented";
public static final String BAD_GATEWAY = "502 Bad Gateway";
Expand Down
74 changes: 74 additions & 0 deletions src/one/nio/ws/WebSocketHeaders.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package one.nio.ws;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import one.nio.http.Request;
import one.nio.util.Base64;

/**
* @author <a href="mailto:[email protected]">Vadim Yelisseyev</a>
*/
public class WebSocketHeaders {
public final static String CONNECTION = "Connection: ";
public final static String UPGRADE = "Upgrade: ";
public final static String KEY = "Sec-WebSocket-Key: ";
public final static String VERSION = "Sec-WebSocket-Version: ";
public final static String ACCEPT = "Sec-WebSocket-Accept: ";
public final static String EXTENSIONS = "Sec-WebSocket-Extensions: ";
public final static String PROTOCOL = "Sec-WebSocket-Protocol: ";

private static final String ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private static final ThreadLocal<MessageDigest> SHA1 = ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
throw new InternalError("SHA-1 not supported on this platform");
}
});

public static boolean isUpgradableRequest(Request request) {
final String upgradeHeader = request.getHeader(WebSocketHeaders.UPGRADE);
final String connectionHeader = request.getHeader(WebSocketHeaders.CONNECTION);
return upgradeHeader != null && upgradeHeader.toLowerCase().contains("websocket") &&
connectionHeader != null && connectionHeader.toLowerCase().contains("upgrade");
}

public static String createVersionHeader(String version) {
return VERSION + version;
}

public static String createAcceptHeader(Request request) {
return ACCEPT + generateHash(request);
}

private static String generateHash(Request request) {
String key = request.getHeader(WebSocketHeaders.KEY);
String acceptSeed = key + ACCEPT_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(StandardCharsets.ISO_8859_1));
return new String(Base64.encode(sha1));
}

private static byte[] sha1(byte[] data) {
MessageDigest digest = SHA1.get();
digest.reset();
return digest.digest(data);
}
}
76 changes: 76 additions & 0 deletions src/one/nio/ws/WebSocketServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package one.nio.ws;

import java.io.IOException;

import one.nio.http.HttpServer;
import one.nio.http.HttpSession;
import one.nio.http.Request;
import one.nio.net.Socket;
import one.nio.ws.message.BinaryMessage;
import one.nio.ws.message.CloseMessage;
import one.nio.ws.message.PingMessage;
import one.nio.ws.message.PongMessage;
import one.nio.ws.message.TextMessage;

/**
* @author <a href="mailto:[email protected]">Vadim Yelisseyev</a>
*/
public class WebSocketServer extends HttpServer {
private final WebSocketServerConfig config;

public WebSocketServer(WebSocketServerConfig config, Object... routers) throws IOException {
super(config, routers);
this.config = config;
}

@Override
public WebSocketSession createSession(Socket socket) {
return new WebSocketSession(socket, this, config);
}

@Override
public void handleRequest(Request request, HttpSession session) throws IOException {
if (config.isWebSocketURI(request.getURI())) {
((WebSocketSession) session).handshake(request);
return;
}

super.handleRequest(request, session);
}

public void handleMessage(WebSocketSession session, PingMessage message) throws IOException {
session.sendMessage(PongMessage.EMPTY);
}

public void handleMessage(WebSocketSession session, PongMessage message) throws IOException {
// nothing by default
}

public void handleMessage(WebSocketSession session, TextMessage message) throws IOException {
// nothing by default
}

public void handleMessage(WebSocketSession session, BinaryMessage message) throws IOException {
// nothing by default
}

public void handleMessage(WebSocketSession session, CloseMessage message) throws IOException {
session.close(CloseMessage.NORMAL);
}
}
39 changes: 39 additions & 0 deletions src/one/nio/ws/WebSocketServerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package one.nio.ws;

import java.util.Collections;
import java.util.Objects;
import java.util.Set;

import one.nio.http.HttpServerConfig;

/**
* @author <a href="mailto:[email protected]">Vadim Yelisseyev</a>
*/
public class WebSocketServerConfig extends HttpServerConfig {
public String websocketBaseUri = "/";
public Set<String> supportedProtocols = Collections.emptySet();

public boolean isWebSocketURI(String uri) {
return Objects.equals(websocketBaseUri, uri);
}

public boolean isSupportedProtocol(String protocol) {
return supportedProtocols.contains(protocol);
}
}
Loading

0 comments on commit b5bc00d

Please sign in to comment.