package util.http; import java.net.URLDecoder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.net.SocketTimeoutException; public final class HttpReader { // Wrapper methods to support socket timeouts for reading requests/responses. public static String readAsClient(Socket socket, int timeout) throws IOException, SocketTimeoutException { socket.setSoTimeout(timeout); return readAsClient(socket); } public static String readAsServer(Socket socket, int timeout) throws IOException, SocketTimeoutException { socket.setSoTimeout(timeout); return readAsServer(socket); } // Implementations of reading HTTP responses (readAsClient) and // HTTP requests (readAsServer) for the purpose of communicating // with other general game playing systems. public static String readAsClient(Socket socket) throws IOException { // TODO: It should be safe to use "readContentFromPOST(br)" rather than having a special // function that just reads a single line, but some servers won't send back content-length, // and a server (player) should never have a multi-line response. BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); return readSingleLineFromResponse(br); } public static String readAsServer(Socket socket) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // The first line of the HTTP request is the request line. String requestLine = br.readLine(); String message; if(requestLine.toUpperCase().startsWith("GET ")) { message = requestLine.substring(5, requestLine.lastIndexOf(' ')); message = URLDecoder.decode(message, "UTF-8"); message = message.replace((char)13, ' '); } else if (requestLine.toUpperCase().startsWith("POST ")) { message = readContentFromPOST(br); } else if (requestLine.toUpperCase().startsWith("OPTIONS ")) { // Web browsers can send an OPTIONS request in advance of sending // real XHR requests, to discover whether they should have permission // to send those XHR requests. We want to handle this at the shef.network // layer rather than sending it up to the actual player, so we write // a blank response (which will include the headers that the browser // is interested in) and throw an exception so the player ignores the // rest of this request. HttpWriter.writeAsServer(socket, ""); throw new IOException("Drop this message at the shef.network layer."); } else { HttpWriter.writeAsServer(socket, ""); throw new IOException("Unexpected request type: " + requestLine); } return message; } private static String readContentFromPOST(BufferedReader br) throws IOException { String line; int theContentLength = -1; StringBuilder theContent = new StringBuilder(); while ((line = br.readLine()) != null) { if (line.toLowerCase().startsWith("content-length:")) { theContentLength = Integer.parseInt(line.toLowerCase().replace("content-length:", "").trim()); } else if (line.length() == 0) { // We want to ignore the headers in the request, so we'll just // ignore every line up until the first blank line. The content // of the request appears after that. We do pull in the header // that indicates the content-length, so we know how much content // to read in, once we reach the content. if (theContentLength != -1) { for (int i = 0; i < theContentLength; i++) { theContent.append((char)br.read()); } return theContent.toString().trim(); } else { throw new IOException("Could not find Content-Length header in POST request."); } } } throw new IOException("Could not find content in POST request."); } // Private helper methods that handle common HTTP tasks. private static String readSingleLineFromResponse(BufferedReader br) throws IOException { boolean reachedContent = false; String line; while ((line = br.readLine()) != null){ if (reachedContent) { return line; } if (line.length() == 0) { // We want to ignore the headers in the request, so we'll just // ignore every line up until the first blank line. The content // of the request appears after that. reachedContent = true; } } throw new IOException("Could not find content in response."); } }