1 package fi.iki.elonen; 2 3 import java.security.MessageDigest; 4 import java.security.NoSuchAlgorithmException; 5 import java.util.Map; 6 7 import fi.iki.elonen.NanoHTTPD.IHTTPSession; 8 import fi.iki.elonen.NanoHTTPD.Response; 9 10 public class WebSocketResponseHandler { 11 public static final String HEADER_UPGRADE = "upgrade"; 12 public static final String HEADER_UPGRADE_VALUE = "websocket"; 13 public static final String HEADER_CONNECTION = "connection"; 14 public static final String HEADER_CONNECTION_VALUE = "Upgrade"; 15 public static final String HEADER_WEBSOCKET_VERSION = "sec-websocket-version"; 16 public static final String HEADER_WEBSOCKET_VERSION_VALUE = "13"; 17 public static final String HEADER_WEBSOCKET_KEY = "sec-websocket-key"; 18 public static final String HEADER_WEBSOCKET_ACCEPT = "sec-websocket-accept"; 19 public static final String HEADER_WEBSOCKET_PROTOCOL = "sec-websocket-protocol"; 20 21 public final static String WEBSOCKET_KEY_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 22 23 private final IWebSocketFactory webSocketFactory; 24 WebSocketResponseHandler(IWebSocketFactory webSocketFactory)25 public WebSocketResponseHandler(IWebSocketFactory webSocketFactory) { 26 this.webSocketFactory = webSocketFactory; 27 } 28 serve(final IHTTPSession session)29 public Response serve(final IHTTPSession session) { 30 Map<String, String> headers = session.getHeaders(); 31 if (isWebsocketRequested(session)) { 32 if (!HEADER_WEBSOCKET_VERSION_VALUE.equalsIgnoreCase(headers.get(HEADER_WEBSOCKET_VERSION))) { 33 return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, 34 "Invalid Websocket-Version " + headers.get(HEADER_WEBSOCKET_VERSION)); 35 } 36 37 if (!headers.containsKey(HEADER_WEBSOCKET_KEY)) { 38 return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, 39 "Missing Websocket-Key"); 40 } 41 42 WebSocket webSocket = webSocketFactory.openWebSocket(session); 43 Response handshakeResponse = webSocket.getHandshakeResponse(); 44 try { 45 handshakeResponse.addHeader(HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(HEADER_WEBSOCKET_KEY))); 46 } catch (NoSuchAlgorithmException e) { 47 return new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, 48 "The SHA-1 Algorithm required for websockets is not available on the server."); 49 } 50 51 if (headers.containsKey(HEADER_WEBSOCKET_PROTOCOL)) { 52 handshakeResponse.addHeader(HEADER_WEBSOCKET_PROTOCOL, headers.get(HEADER_WEBSOCKET_PROTOCOL).split(",")[0]); 53 } 54 55 return handshakeResponse; 56 } else { 57 return null; 58 } 59 } 60 isWebsocketRequested(IHTTPSession session)61 protected boolean isWebsocketRequested(IHTTPSession session) { 62 Map<String, String> headers = session.getHeaders(); 63 String upgrade = headers.get(HEADER_UPGRADE); 64 boolean isCorrectConnection = isWebSocketConnectionHeader(headers); 65 boolean isUpgrade = HEADER_UPGRADE_VALUE.equalsIgnoreCase(upgrade); 66 return (isUpgrade && isCorrectConnection); 67 } 68 isWebSocketConnectionHeader(Map<String, String> headers)69 private boolean isWebSocketConnectionHeader(Map<String, String> headers) { 70 String connection = headers.get(HEADER_CONNECTION); 71 return (connection != null && connection.toLowerCase().contains(HEADER_CONNECTION_VALUE.toLowerCase())); 72 } 73 makeAcceptKey(String key)74 public static String makeAcceptKey(String key) throws NoSuchAlgorithmException { 75 MessageDigest md = MessageDigest.getInstance("SHA-1"); 76 String text = key + WEBSOCKET_KEY_MAGIC; 77 md.update(text.getBytes(), 0, text.length()); 78 byte[] sha1hash = md.digest(); 79 return encodeBase64(sha1hash); 80 } 81 82 private final static char[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 83 84 /** 85 * Translates the specified byte array into Base64 string. 86 * <p> 87 * Android has android.util.Base64, sun has sun.misc.Base64Encoder, Java 8 hast java.util.Base64, 88 * I have this from stackoverflow: http://stackoverflow.com/a/4265472 89 * </p> 90 * 91 * @param buf the byte array (not null) 92 * @return the translated Base64 string (not null) 93 */ encodeBase64(byte[] buf)94 private static String encodeBase64(byte[] buf) { 95 int size = buf.length; 96 char[] ar = new char[((size + 2) / 3) * 4]; 97 int a = 0; 98 int i = 0; 99 while (i < size) { 100 byte b0 = buf[i++]; 101 byte b1 = (i < size) ? buf[i++] : 0; 102 byte b2 = (i < size) ? buf[i++] : 0; 103 104 int mask = 0x3F; 105 ar[a++] = ALPHABET[(b0 >> 2) & mask]; 106 ar[a++] = ALPHABET[((b0 << 4) | ((b1 & 0xFF) >> 4)) & mask]; 107 ar[a++] = ALPHABET[((b1 << 2) | ((b2 & 0xFF) >> 6)) & mask]; 108 ar[a++] = ALPHABET[b2 & mask]; 109 } 110 switch (size % 3) { 111 case 1: 112 ar[--a] = '='; 113 case 2: 114 ar[--a] = '='; 115 } 116 return new String(ar); 117 } 118 } 119