HTTPReq{{ /** Default Socket connection timeout (20 seconds) */ public static final int DEFAULT_CONN_TIMEOUT = 20*1000; /** Default Socket Response timeout (0.5 second) */ public static final int DEFAULT_RESP_TIMEOUT = 500; /** Create an HTTP Request with a given Header, MessageHeaders, and Body */ public static HTTPReq create(HTTPHead req, List hds, String body){ return createNoLen(req, hds.append(new MsgHead("Content-Length",""+body.length())), body); } /** Create an HTTP Request with a given Header, MessageHeaders, and Body */ private static HTTPReq createNoLen(HTTPHead req, List hds, String body){ return new HTTPReq(req, hds, new ident(body)); } /** Create an HTTP Get Request for the given (relative) URL. Then use * {@link #send(String,int) Send} for a Request/Response pair. */ public static HTTPReq Get(String url) throws ParseException { return create(HTTPHead.Get(URL.parse(url)), List.create(), ""); } /** Create an HTTP Get Request for the given relative URL, to be sent to the * given Host. Use this method for raw Socket/Connection sends, but use * {@link #send(String,int) Send} for a Request/Response pair. */ public static HTTPReq Get(String url, String host) throws ParseException { return create(HTTPHead.Get(URL.parse(url)), hostHeader(host), ""); } /** Create an HTTP Head Request for the given (relative) URL. Then use * {@link #send(String,int) Send} for a Request/Response pair. */ public static HTTPReq Head(String url) throws ParseException { return create(HTTPHead.Get(URL.parse(url)), List.create(), ""); } /** Create an HTTP Head Request for the given relative URL, to be sent to the * given Host. Use this method for raw Socket/Connection sends, but use * {@link #send(String,int) Send} for a Request/Response pair. */ public static HTTPReq Head(String url, String host) throws ParseException { return create(HTTPHead.Head(URL.parse(url)), hostHeader(host), ""); } /** Create an HTTP Post Request to the given (relative) URL. Then use * {@link #send(String,int) Send} for a Request/Response pair. */ public static HTTPReq Post(String url, String body) throws ParseException { return create(HTTPHead.Post(URL.parse(url)), List.create(), body); } /** Create an HTTP Post Request to the given relative URL, to be sent to the * given Host. Use this method for raw Socket/Connection sends, but use * {@link #send(String,int) Send} for a Request Response Pair. */ public static HTTPReq Post(String url, String host, String body) throws ParseException { return create(HTTPHead.Post(URL.parse(url)), hostHeader(host), body); } private static List hostHeader(String h) { return List.create(new MsgHead(new ident("Host"), new ident(h))); } /** Add the given Message Headers to this Request */ private HTTPReq addHeaders(List hs){ return createNoLen(head,hs.append(keys),""+body); } /** Add a given Message Headers to this Request */ public HTTPReq addHeader(String key, String val){ return createNoLen(head,keys.append(new MsgHead(key,val)),""+body); } /** Request Types */ public static enum ReqType{ GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, OPTIONS, OTHER }; /** Return this Request's type */ public ReqType getType(){ return head.getType(); } /** Return the URL arguments (key/value) for this request. The URL arguments * follow the base URL after a '?'. * E.g.: /foo/bar?bazzle=13&wizwoz='hello' */ public Map urlArgs(){ return head.url.urlArgs(); } /** Return the Body arguments (key/value) from this request. The Body arguments * are on a single line of the body, like URLArgs, but without the '?' */ public Map bodyArgs(){ return splitArgs(""+body); } /** Return the relative URL, without any URL arguments */ public String trimmedUrl(){ return head.url.trimArgs(); } /** Send this Request to the given Server/Port, and return its Response */ public HTTPResp send(String server, int port) throws IOException { return send(server,port,0); } /** Send this Request to the given Server/Port, and return its Response with * the given response timeout. If the server does not respond in the given * time a Socket exception will eb thrown */ public HTTPResp send(String server, int port, int respTimeout) throws IOException { return send(server,port,HTTPReq.DEFAULT_CONN_TIMEOUT,respTimeout,0); } /** Send this Request to the given Server/Port, and return its Response with * the given response timeout, and limit the max response size to maxResp */ public HTTPResp send(String server, int port, int respTimeout, long maxResp) throws IOException { return send(server,port,HTTPReq.DEFAULT_CONN_TIMEOUT, respTimeout, maxResp); } /** Send this Request to the given Server/Port, and return its * Response, wait for the specified timout (in Milliseconds), and only * accept up to "maxResp" bytes */ public HTTPResp send(String server, int port, int connTimeout, int respTimeout, long maxResp) throws IOException { Socket sock = new Socket(); // Connect to the Server... timeout if not available sock.connect(new InetSocketAddress(server, port), connTimeout); // Add the Host header and send it to a socket addHeaders(hostHeader(server+":"+port)).toSocket(sock); // Read the response from the Socket, but timeout if needed HTTPResp res = HTTPResp.fromSocket(sock, respTimeout, maxResp); sock.close(); return res; } /** Write this Request to the given Socket */ public void toSocket(Socket s){ try{ PrintWriter outt = new PrintWriter(s.getOutputStream()); outt.print(this.toString()); outt.flush(); s.shutdownOutput(); }catch(IOException e){ throw new RuntimeException(e); } } /** Read a request from a Socket, without a timeout (wait forever) */ public static HTTPReq fromSocket(Socket s){ return fromSocket(s,0,0); } /** Read a request from a Socket, wait forever, but limit the transmission */ public static HTTPReq fromSocket(Socket s, long maxBytes){ return fromSocket(s,0,maxBytes); } /** Read a request from a Socket, timeout if needed */ public static HTTPReq fromSocket(Socket s, int respTimeout, long maxBytes){ try{ // Set a timeout on the response s.setSoTimeout(DEFAULT_RESP_TIMEOUT); HTTPReq req = fromInputStream(s.getInputStream(),respTimeout,maxBytes); s.shutdownInput(); return req; }catch(IOException e){ throw new RuntimeException(e); } } /** Read a Request from an InputStream with the given Timeout */ private static HTTPReq fromInputStream(InputStream inpt, long timeout, long maxBytes){ try{ BufferedReader inn = new BufferedReader(new InputStreamReader(inpt)); String first = readLine(inn,timeout); if(first == null)throw new RuntimeException("Empty HTTP Header"); HTTPHead h = HTTPHead.parse(first); HTTPReq.CountHds headers = HTTPReq.parseMsgHeads(inn,timeout,maxBytes,maxBytes-(first.length()+1)); return new HTTPReq(h, headers.heads, HTTPReq.parseBody(inn,timeout,maxBytes, maxBytes-(first.length()+1+headers.size))); }catch(RuntimeException e){ throw e; }catch(Exception e){ throw new RuntimeException(e); } } /** Keep track of the bytes read for the headers */ static class CountHds{ public long size; public List heads; public CountHds(long s, List hds){ size = s; heads = hds; } public CountHds add(long s, MsgHead hd){ return new CountHds(s+size, heads.push(hd)); } } /** Parse a List of Message Headers */ static CountHds parseMsgHeads(BufferedReader in, long timeout, long maxBytes, long bytesLeft) throws Exception{ checkMaxBytes(maxBytes, bytesLeft); String line; if((line = readLine(in,timeout)) == null || line.length() == 0) return new CountHds(0, List.create()); int colon = line.indexOf(":"); return parseMsgHeads(in,timeout,maxBytes,bytesLeft-(line.length()+1)) .add(line.length()+1, new MsgHead(new ident(line.substring(0,colon)), new ident(line.substring(colon+2)))); } /** Read a single Line */ static String readLine(BufferedReader inn, long timeout) throws IOException{ long start = System.currentTimeMillis(); String line = ""; while(true){ line = null; try{ line = inn.readLine(); return line; }catch(java.net.SocketTimeoutException e){ if(timeout > 0 && (System.currentTimeMillis()-start) > timeout) throw e; } } } static void checkMaxBytes(long maxBytes, long bytesLeft){ if(maxBytes > 0 && bytesLeft < 0) throw new RuntimeException("HTTP Max Size Reached"); } /** Parse the Body of a Request */ static ident parseBody(BufferedReader inn, long timeout, long maxBytes, long bytesLeft) throws Exception{ checkMaxBytes(maxBytes, bytesLeft); long start = System.currentTimeMillis(); StringBuffer sb = new StringBuffer(); char[] buff = new char[1024]; int many; do{ many = 0; try{ many = inn.read(buff); if(many>0){ sb.append(buff,0,many); bytesLeft -= many; checkMaxBytes(maxBytes, bytesLeft); } }catch(java.net.SocketTimeoutException e){ if(timeout > 0 && (System.currentTimeMillis()-start) > timeout) throw new RuntimeException(e); } }while(inn.ready() || many > 0); return new ident(sb.toString()); } /** Return the Body of this Request */ public String getBodyString(){ return body.toString(); } /** Return the Headers of this Request as a Map */ public Map getHeaders(){ return getHeaders(keys); } /** Return the Headers of this Request as a Map */ static Map getHeaders(List hds){ return hds.fold(new List.Fold>(){ public Map fold(MsgHead h, Map m){ return m.put(""+h.key,""+h.value); } }, Map.create()); } /** Split an argument String into key/value Map */ static Map splitArgs(String s){ return List.create(s.split("&")).fold(new List.Fold>(){ public Map fold(String p, Map m){ String[] kv = p.split("="); if(kv.length < 2)return m; return m.put(decodeURL(kv[0]),decodeURL(kv[1])); } }, Map.create()); } /** Decode an encoded URL */ public static String decodeURL(String url){ try{ return java.net.URLDecoder.decode(url, "UTF-8"); }catch(Exception e){ throw new RuntimeException(e); } } /** Encode a (possibly special) URL */ public static String encodeURL(String url){ try{ return java.net.URLEncoder.encode(url, "UTF-8"); }catch(Exception e){ throw new RuntimeException(e); } } }} HTTPResp{{ /** Basic Response Numbers */ public static final int OK = 200, NOT_FOUND = 404, MIN_OK = OK, MAX_OK = 299, MIN_ERROR = 400, MIN_SERVER_ERROR = 500, MAX_ERROR = 599; /** Typical HTTP Version */ public static final HTTPVer VER = new HTTPVer(1.0); /** Create a response with the given number, description, headers, and body */ public static HTTPResp create(int resp, String desc, List hds, String body){ return createNoLen(resp, new ident(desc), hds.append(new MsgHead("Content-Length",""+body.length())), new ident(body)); } /** Create a response without adding the Length */ private static HTTPResp createNoLen(int resp, ident label, List hds, ident body){ return new HTTPResp(VER, resp, label, hds, body); } /** Create an OK Response with the given Content-Type/Body */ public static HTTPResp ok(String type, String body){ return create(OK, "OK", commonHeaders(type), body); } /** Common Headers: Date, Content-Type */ private static List commonHeaders(String type){ return List.create( new MsgHead("Date",""+new java.util.Date()), new MsgHead("Content-Type",type)); } /** Create an (empty) Error Response */ public static HTTPResp error(){ return textError(""); } /** Create an Error Response with t*/ public static HTTPResp error(String body){ return textError(body); } /** Create an (empty) Error Response */ public static HTTPResp error(int errCode, String msg, String body){ return textError(errCode,msg,body); } /** Create an TEXT Error (File Not Found" Response with a Body */ public static HTTPResp textError(String body){ return textError(NOT_FOUND, "Not Found", body); } /** Create an Texr Error Response with the given Code, Message and Body */ public static HTTPResp textError(int errCode, String msg, String body){ return create(errCode, msg, commonHeaders("text/plain"), body); } /** Create an HTML Error "File Not Found") Response with a Body */ public static HTTPResp htmlError(String body){ return htmlError(NOT_FOUND, "Not Found", body); } /** Create an HTML Error Response with a Body */ public static HTTPResp htmlError(int errCode, String msg, String body){ return create(errCode, msg, commonHeaders("text/html"), body); } /** Create a Plain Text Response */ public static HTTPResp textResponse(String text){ return ok("text/plain", text); } /** Create an HTML Response */ public static HTTPResp htmlResponse(String text){ return ok("text/html", text); } /** Add a given Message Header to this Response */ public HTTPResp addHeader(String key, String val){ return createNoLen(resp,label,keys.append(new MsgHead(key,val)),body); } /** Is this Response OK? */ public boolean isOK(){ return resp >= MIN_OK && resp <= MAX_OK; } /** Is this Response an Error? */ public boolean isError(){ return resp >= MIN_ERROR && resp <= MAX_ERROR; } /** Is this Response a Server Error? */ public boolean isServerError(){ return resp >= MIN_SERVER_ERROR && resp <= MAX_ERROR; } /** Is this Response a Client Error? */ public boolean isClientError(){ return resp >= MIN_ERROR && resp < MIN_SERVER_ERROR; } /** Write a Response to a Socket */ public void toSocket(Socket s){ try{ PrintWriter outt = new PrintWriter(s.getOutputStream()); outt.print(this.toString()); outt.flush(); s.shutdownOutput(); }catch(IOException e){ throw new RuntimeException(e); } } /** Read a Response from a Socket */ public static HTTPResp fromSocket(Socket s){ return fromSocket(s,0); } /** Read a Response from a Socket */ public static HTTPResp fromSocket(Socket s, long maxBytes){ return fromSocket(s,0,maxBytes); } /** Read a Response from a Socket */ public static HTTPResp fromSocket(Socket s, int respTimeout, long maxBytes){ try{ s.setSoTimeout(respTimeout); HTTPResp resp = fromInputStream(s.getInputStream(), respTimeout, maxBytes); s.shutdownInput(); return resp; }catch(IOException e){ throw new RuntimeException(e); } } /** Read a Response from an Input Stream */ public static HTTPResp fromInputStream(InputStream inpt){ return fromInputStream(inpt,0); } /** Read a Response from an Input Stream */ public static HTTPResp fromInputStream(InputStream inpt, long maxBytes){ return fromInputStream(inpt,0,maxBytes); } /** Read a Response from an Input Stream */ public static HTTPResp fromInputStream(InputStream inpt, long timeout, long maxBytes){ try{ BufferedReader inn = new BufferedReader(new InputStreamReader(inpt)); String first = HTTPReq.readLine(inn,timeout); if(first == null)throw new RuntimeException("Empty HTTP Header"); HTTPVer v = HTTPVer.parse(first); first = first.substring(first.indexOf(' ')+1); int b = first.indexOf(' '), rnum = Integer.parseInt(first.substring(0,b)); HTTPReq.CountHds headers = HTTPReq.parseMsgHeads(inn,timeout,maxBytes,maxBytes-(first.length()+1)); return new HTTPResp(v, rnum, new ident(first.substring(b+1)), headers.heads, HTTPReq.parseBody(inn,timeout,maxBytes,maxBytes-(first.length()+1+headers.size))); }catch(RuntimeException e){ throw e; }catch(Exception e){ throw new RuntimeException(e); } } /** Get the body of this Response */ public String getBodyString(){ return ""+body; } /** Return the headers (key/value Map) of this Response */ public Map getHeaders(){ return HTTPReq.getHeaders(keys); } }} HTTPHead{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.OTHER; } /** Return a Get Request for the given URL */ public static HTTPHead Get(URL url) { return new GetReq(url, HTTPResp.VER); } /** Return a Head Request for the given URL */ public static HTTPHead Head(URL url) { return new HeadReq(url, HTTPResp.VER); } /** Return a Post Request for the given URL */ public static HTTPHead Post(URL url) { return new PostReq(url, HTTPResp.VER); } }} GetReq{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.GET; } }} PostReq{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.POST; } }} HeadReq{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.HEAD; } }} PutReq{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.PUT; } }} DeleteReq{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.DELETE; } }} TraceReq{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.TRACE; } }} ConnectReq{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.CONNECT; } }} OptionsReq{{ /** Get the ReqType of this Request */ public HTTPReq.ReqType getType(){ return HTTPReq.ReqType.OPTIONS; } }} MsgHead{{ /** Create a MsgHeader from key/value Strings */ public MsgHead(String k, String v){ this(new ident(k), new ident(v)); } }} URL{{ /** Is this URL Empty? */ public boolean isEmpty(){ return false; } /** Return the URLs Arguments */ public abstract Map urlArgs(); /** Remove the URLs Arguments */ public abstract String trimArgs(); }} NoURL{{ /** Is this URL Empty? */ public boolean isEmpty(){ return true; } /** Return the URLs Arguments */ public Map urlArgs(){ return Map.create(); } /** Remove the URLs Arguments */ public String trimArgs(){ return ""; } }} BaseURL{{ /** Return the URLs Arguments */ public Map urlArgs(){ return rest.urlArgs(); } /** Remove the URL Arguments */ public String trimArgs(){ return "/"+rest.trimArgs(); } }} MidURL{{ /** Return the URLs Arguments */ public Map urlArgs(){ Map r = rest.urlArgs(); if(!rest.isEmpty())return r; String idS = id.toString(); int amp = idS.indexOf('?'); if(amp<0)return r; idS = idS.substring(idS.indexOf('?')+1).trim(); return HTTPReq.splitArgs(idS); } /** Remove the URLs Arguments */ public String trimArgs(){ if(!rest.isEmpty())return id+rest.trimArgs(); String idS = id.toString(); int amp = idS.indexOf('?'); return amp<0 ? idS: idS.substring(0,amp); } }}