Java HTTP server sending chunked response -
i working on java application has built in http server, @ moment server implemented using serversocketchannel, listens on port 1694 requests:
msvrch = serversocketchannel.open(); msvrch.socket().bind(new inetsocketaddress(mintport)); msvrch.configureblocking(false);
a thread installed manage requests , responses:
thread thrd = new thread(msgreceiver); thrd.setuncaughtexceptionhandler(exceptionhandler); thrd.start();
the thread quite simple:
runnable msgreceiver = new runnable() { @override public void run() { try{ while( !thread.interrupted() ) { //sleep short period between checks new requests try{ thread.sleep(delay_between_accepts); } catch(exception ex) { ex.printstacktrace(); } socketchannel clich = msvrch.accept(); if ( blnexit() == true ) { break; } if ( clich == null ) { continue; } processrequest(clich.socket()); } } catch (ioexception ex) { ex.printstacktrace(); } { logmsg(terminating_thread + "for accepting cluster connections", true); if ( msvrch != null ) { try { msvrch.close(); } catch (ioexception ex) { ex.printstacktrace(); } msvrch = null; } } } };
the main bulk of code dealing response in function processrequest:
private void processrequest(socket sck) { try { //ajax parameters final string ajax_id = "ajmid"; //the 'handler key' used decode response final string handler_key = "hkey"; //message payload final string payload = "payload"; //post input buffer size final int request_buffer_size = 4096; //double carriage return marks end of headers final string crlf = "\r\n"; bufferedreader in = new bufferedreader(new inputstreamreader(sck.getinputstream())); string stramid = null, strhkey = null, strrequest; char[] chrbuffer = new char[request_buffer_size]; stringbuffer sbrequest = new stringbuffer(); emsgtypes etype = emsgtypes.unknown; clshttpparameters objparams = null; int intpos, intcount; //extract entire request, including headers if ( (intcount = in.read(chrbuffer)) == 0 ) { throw new exception("cannot read request!"); } sbrequest.append(chrbuffer, 0, intcount); strrequest = sbrequest.tostring(); //what method being used request? if ( strrequest.startswith(http_get) ) { //the request should end http marker, remove before trying interpret data if ( strrequest.indexof(http_marker) != -1 ) { strrequest = strrequest.substring(0, strrequest.indexof(http_marker)).trim(); } //look data marker if ( (intpos = strrequest.indexof(http_data_start)) >= 0 ) { //data present in query, skip start of data strrequest = strrequest.substring(intpos + 1); } else { //remove method indicator strrequest = strrequest.substring(http_get.length()); } } else if ( strrequest.startswith(http_post) ) { //discard headers , jump data if ( (intpos = strrequest.lastindexof(crlf)) >= 0 ) { strrequest = strrequest.substring(intpos + crlf.length()); } } if ( strrequest.length() > 1 ) { //extract parameters objparams = new clshttpparameters(strrequest); } if ( strrequest.startswith("/") == true ) { //look document reference strrequest = strrequest.substring(1); etype = emsgtypes.send_doc; } if ( objparams != null ) { //transfer payload request string strpayload = objparams.getvalue(payload); if ( strpayload != null ) { byte[] arybytpayload = base64.decodebase64(strpayload.getbytes()); strrequest = new string(arybytpayload); stramid = objparams.getvalue(ajax_id); strhkey = objparams.getvalue(handler_key); } } if ( etype == emsgtypes.unknown && strrequest.startswith("{") && strrequest.endswith("}") ) { //the payload json, there type parameter? string strtype = strgetjsonitem(strrequest, json_lbl_type); if ( strtype != null && strtype.length() > 0 ) { //decode type etype = emsgtypes.valueof(strtype.touppercase().trim()); //what system message from? string strip = strgetjsonitem(strrequest, json_lbl_ip) ,strmac = strgetjsonitem(strrequest, json_lbl_mac); if ( strip != null && strip.length() > 0 && strmac != null && strmac.length() > 0 ) { //is system known in cluster? clsipmon objsystem = objaddsystocluster(strip, strmac); if ( objsystem != null ) { //update date/time stamp of remote system objsystem.touch(); } //this internal cluster message, no response required return; } } } string strcontenttype = null, strresppayload = null; outputstream out = sck.getoutputstream(); byte[] arybytresponse = null; boolean blnshutdown = false; out.write("http/1.0 200\n".getbytes()); switch( etype ) { case send_doc: if ( strrequest.length() <= 1 ) { strrequest = html_root + default_doc; } else { strrequest = html_root + strrequest; } logmsg("http request for: " + strrequest, true); if ( strrequest.tolowercase().endswith(".css") == true ) { strcontenttype = mime_css; } else if ( strrequest.tolowercase().endswith(".gif") == true ) { strcontenttype = mime_gif; } else if ( strrequest.tolowercase().endswith(".jpg") == true ) { strcontenttype = mime_jpg; } else if ( strrequest.tolowercase().endswith(".js") == true ) { strcontenttype = mime_js; } else if ( strrequest.tolowercase().endswith(".png") == true ) { strcontenttype = mime_png; } else if ( strrequest.tolowercase().endswith(".html") == true || strrequest.tolowercase().endswith(".htm") == true ) { strcontenttype = mime_html; } file objfile = new file(strrequest); if ( objfile.exists() == true ) { fileinputstream objfis = new fileinputstream(objfile); if ( objfis != null ) { arybytresponse = new byte[(int)objfile.length()]; if ( objfis.read(arybytresponse) == 0 ) { arybytresponse = null; } objfis.close(); } } break; case channel_sts: strresppayload = strchannelstatus(strrequest); strcontenttype = mime_json; break; case cluster_sts: strresppayload = strclusterstatus(); strcontenttype = mime_json; break; case module_sts: strresppayload = strmodulestatus(strrequest); strcontenttype = mime_json; break; case network_inf: strresppayload = strnetworkinfo(strrequest); strcontenttype = mime_json; break; case node_sts: strresppayload = strnodestatus(strrequest); strcontenttype = mime_json; break; case poll_sts: strresppayload = strpollstatus(strrequest); strcontenttype = mime_json; break; case sys_sts: //issue system status strresppayload = strappstatus(); strcontenttype = mime_json; break; case shutdown: //issue instruction restart system strresppayload = "shutdown in progress!"; strcontenttype = mime_plain; //flag shutdown has been requested blnshutdown = true; break; default: } if ( strresppayload != null ) { //convert response string byte array arybytresponse = strresppayload.getbytes(); system.out.println("[ " + strresppayload.length() + " ]: " + strresppayload); //hack } if ( arybytresponse != null && arybytresponse.length > 0 ) { if ( strcontenttype == mime_json ) { string strresponse = "{"; if ( stramid != null ) { //include request ajax message id in response if ( strresponse.length() > 1 ) { strresponse += ","; } strresponse += "\"" + ajax_id + "\":" + stramid; } if ( strhkey != null ) { if ( strresponse.length() > 1 ) { strresponse += ","; } strresponse += "\"" + handler_key + "\":\"" + strhkey + "\""; } if ( strresponse.length() > 1 ) { strresponse += ","; } strresponse += "\"payload\":" + new string(arybytresponse) + "}"; arybytresponse = strresponse.getbytes(); } string strheaders = ""; if ( strcontenttype != null ) { strheaders += "content-type: " + strcontenttype + "\n"; } strheaders += "content-length: " + arybytresponse.length + "\n" + "access-control-allow-origin: *\n" + "access-control-allow-methods: post, get, options, delete, put\n" + "access-control-allow-credentials: true\n" + "keep-alive: timeout=2, max=100\n" + "cache-control: no-cache\n" + "pragma: no-cache\n\n"; out.write(strheaders.getbytes()); out.write(arybytresponse); out.flush(); } out.close(); sck.close(); if ( blnshutdown == true ) { string strsystem = mobjlocalip.strgetip(); if ( strsystem.compareto(mobjlocalip.strgetip()) != 0 ) { //specified system not local system, issue message remote system. broadcastmessage("{\"" + json_lbl_type + "\":\"" + emsgtypes.shutdown + "\"" + ",\"" + json_lbl_time + "\":\"" + clstimeman.lngtimenow() + "\"}"); } else { //shutdown addressed local system if ( getos().indexof("linux") >= 0 ) { //to do!!! } else if ( getos().indexof("win") >= 0 ) { runtime runtime = runtime.getruntime(); runtime.exec("shutdown /r /c \"shutdown request\" /t 0 /f"); system.exit(exitcode_requested_shutdown); } } } } catch (exception ex) { } { if (sck != null) { try { sck.close(); } catch (ioexception ex) { ex.printstacktrace(); } } } }
i implemented chunked response, @ present chunked responses not supported code above.
[edit] i've tried implement chunked response adding method:
/** * @param strdata - data split chunks * @return string array containing chunks */ public static string[] arystrchunkdata(string strdata) { int intchunks = (strdata.length() / chunk_threshold_bytesize) + 1; string[] arystrchunks = new string[intchunks]; int intlength = strdata.length(), intpos = 0; for( int c=0; c<arystrchunks.length; c++ ) { if ( intpos < intlength ) { //extract chunk data int intend = math.min(intlength - 1, intpos + chunk_threshold_bytesize); arystrchunks[c] = strdata.substring(intpos, intend); } //advance data position next chunk intpos += chunk_threshold_bytesize; } return arystrchunks; }
the modified processrequest looks this:
private void processrequest(socket sck) { try { //ajax parameters final string ajax_id = "ajmid"; //the 'handler key' used decode response final string handler_key = "hkey"; //message payload final string payload = "payload"; //post input buffer size final int request_buffer_size = 4096; //double carriage return marks end of headers final string crlf = "\r\n"; bufferedreader in = new bufferedreader(new inputstreamreader(sck.getinputstream())); string stramid = null, strhkey = null, strrequest; char[] chrbuffer = new char[request_buffer_size]; stringbuffer sbrequest = new stringbuffer(); emsgtypes etype = emsgtypes.unknown; clshttpparameters objparams = null; int intpos, intcount; //extract entire request, including headers if ( (intcount = in.read(chrbuffer)) == 0 ) { throw new exception("cannot read request!"); } sbrequest.append(chrbuffer, 0, intcount); strrequest = sbrequest.tostring(); //what method being used request? if ( strrequest.startswith(http_get) ) { //the request should end http marker, remove before trying interpret data if ( strrequest.indexof(http_marker) != -1 ) { strrequest = strrequest.substring(0, strrequest.indexof(http_marker)).trim(); } //look data marker if ( (intpos = strrequest.indexof(http_data_start)) >= 0 ) { //data present in query, skip start of data strrequest = strrequest.substring(intpos + 1); } else { //remove method indicator strrequest = strrequest.substring(http_get.length()); } } else if ( strrequest.startswith(http_post) ) { //discard headers , jump data if ( (intpos = strrequest.lastindexof(crlf)) >= 0 ) { strrequest = strrequest.substring(intpos + crlf.length()); } } if ( strrequest.length() > 1 ) { //extract parameters objparams = new clshttpparameters(strrequest); } if ( strrequest.startswith("/") == true ) { //look document reference strrequest = strrequest.substring(1); etype = emsgtypes.send_doc; } if ( objparams != null ) { //transfer payload request string strpayload = objparams.getvalue(payload); if ( strpayload != null ) { byte[] arybytpayload = base64.decodebase64(strpayload.getbytes()); strrequest = new string(arybytpayload); stramid = objparams.getvalue(ajax_id); strhkey = objparams.getvalue(handler_key); } } if ( etype == emsgtypes.unknown && strrequest.startswith("{") && strrequest.endswith("}") ) { //the payload json, there type parameter? string strtype = strgetjsonitem(strrequest, json_lbl_type); if ( strtype != null && strtype.length() > 0 ) { //decode type etype = emsgtypes.valueof(strtype.touppercase().trim()); //what system message from? string strip = strgetjsonitem(strrequest, json_lbl_ip) ,strmac = strgetjsonitem(strrequest, json_lbl_mac); if ( strip != null && strip.length() > 0 && strmac != null && strmac.length() > 0 ) { //is system known in cluster? clsipmon objsystem = objaddsystocluster(strip, strmac); if ( objsystem != null ) { //update date/time stamp of remote system objsystem.touch(); } //this internal cluster message, no response required return; } } } string strcontenttype = null, strresppayload = null; outputstream out = sck.getoutputstream(); byte[] arybytresponse = null; boolean blnshutdown = false; //start writing headers string strheaders = "http/1.0 200\n" + "date: " + (new date()).tostring() + "\n" + "access-control-allow-origin: *\n" + "access-control-allow-methods: post, get, options, delete, put\n" + "access-control-allow-credentials: true\n" + "keep-alive: timeout=2, max=100\n" + "cache-control: no-cache\n" + "pragma: no-cache\n"; out.write(strheaders.getbytes()); strheaders = ""; switch( etype ) { case send_doc: if ( strrequest.length() <= 1 ) { strrequest = html_root + default_doc; } else { strrequest = html_root + strrequest; } logmsg("http request for: " + strrequest, true); if ( strrequest.tolowercase().endswith(".css") == true ) { strcontenttype = mime_css; } else if ( strrequest.tolowercase().endswith(".gif") == true ) { strcontenttype = mime_gif; } else if ( strrequest.tolowercase().endswith(".jpg") == true ) { strcontenttype = mime_jpg; } else if ( strrequest.tolowercase().endswith(".js") == true ) { strcontenttype = mime_js; } else if ( strrequest.tolowercase().endswith(".png") == true ) { strcontenttype = mime_png; } else if ( strrequest.tolowercase().endswith(".html") == true || strrequest.tolowercase().endswith(".htm") == true ) { strcontenttype = mime_html; } file objfile = new file(strrequest); if ( objfile.exists() == true ) { fileinputstream objfis = new fileinputstream(objfile); if ( objfis != null ) { arybytresponse = new byte[(int)objfile.length()]; if ( objfis.read(arybytresponse) == 0 ) { arybytresponse = null; } objfis.close(); } } break; case channel_sts: strresppayload = strchannelstatus(strrequest); strcontenttype = mime_json; break; case cluster_sts: strresppayload = strclusterstatus(); strcontenttype = mime_json; break; case module_sts: strresppayload = strmodulestatus(strrequest); strcontenttype = mime_json; break; case network_inf: strresppayload = strnetworkinfo(strrequest); strcontenttype = mime_json; break; case node_sts: strresppayload = strnodestatus(strrequest); strcontenttype = mime_json; break; case poll_sts: strresppayload = strpollstatus(strrequest); strcontenttype = mime_json; break; case sys_sts: //issue system status strresppayload = strappstatus(); strcontenttype = mime_json; break; case shutdown: //issue instruction restart system strresppayload = "shutdown in progress!"; strcontenttype = mime_plain; //flag shutdown has been requested blnshutdown = true; break; default: } if ( strresppayload != null ) { //convert response string byte array arybytresponse = strresppayload.getbytes(); } if ( arybytresponse != null && arybytresponse.length > 0 ) { boolean blnchunked = false; if ( strcontenttype != null ) { strheaders += "content-type: " + strcontenttype + "\n"; } if ( strcontenttype == mime_json ) { string strresponse = "{"; if ( stramid != null ) { //include request ajax message id in response if ( strresponse.length() > 1 ) { strresponse += ","; } strresponse += "\"" + ajax_id + "\":" + stramid; } if ( strhkey != null ) { if ( strresponse.length() > 1 ) { strresponse += ","; } strresponse += "\"" + handler_key + "\":\"" + strhkey + "\""; } if ( strresponse.length() > 1 ) { strresponse += ","; } strresponse += "\"payload\":" + new string(arybytresponse) + "}"; //how big response? if ( strresponse.length() > chunk_threshold_bytesize ) { blnchunked = true; strheaders += "transfer-encoding: chunked\n\n"; out.write(strheaders.getbytes()); //slice string chunks string[] arystrchunks = arystrchunkdata(strresponse); for( int c=0; c<arystrchunks.length; c++ ) { string strchunk = arystrchunks[c]; if ( strchunk != null ) { string strlength = integer.tohexstring(strchunk.length()) + "\r\n"; strchunk += "\r\n"; out.write(strlength.getbytes()); out.write(strchunk.getbytes()); } } //last chunk 0 bytes out.write("0\r\n\r\n".getbytes()); } else { arybytresponse = strresponse.getbytes(); } } if ( blnchunked == false ) { strheaders += "content-length: " + arybytresponse.length + "\n\n"; out.write(strheaders.getbytes()); out.write(arybytresponse); } out.flush(); } out.close(); sck.close(); if ( blnshutdown == true ) { string strsystem = mobjlocalip.strgetip(); if ( strsystem.compareto(mobjlocalip.strgetip()) != 0 ) { //specified system not local system, issue message remote system. broadcastmessage("{\"" + json_lbl_type + "\":\"" + emsgtypes.shutdown + "\"" + ",\"" + json_lbl_time + "\":\"" + clstimeman.lngtimenow() + "\"}"); } else { //shutdown addressed local system if ( getos().indexof("linux") >= 0 ) { //to do!!! } else if ( getos().indexof("win") >= 0 ) { runtime runtime = runtime.getruntime(); runtime.exec("shutdown /r /c \"shutdown request\" /t 0 /f"); system.exit(exitcode_requested_shutdown); } } } } catch (exception ex) { } { if (sck != null) { try { sck.close(); } catch (ioexception ex) { ex.printstacktrace(); } } } }
i've read several specifications chunked responses , far can tell sending data in correct format, don't receive in browser.
i may have wrongly assume browser correctly piece chunks one, wrong. client side handler looks this:
this.responsehandler = function() { try { if ( mobjhttp == null || !(mobjhttp.readystate == 4 && mobjhttp.status == 200) || !(mstrresponsetext = mobjhttp.responsetext) || mstrresponsetext.length == 0 ) { //not ready or no response decode return; } //do response } catch( ex ) { t.error("responsehandler:", ex); }
};
this handler set-up elsewhere in object:
mobjhttp.onreadystatechange = this.responsehandler;
solved, not sure why, removing header:
transfer-encoding: chunked
and chunk lengths @ beginning of each chunk resolved issue, still write data in 768 byte chunks. works reliably , well.
not sure why had this.
final method produce chunks data string:
public static string[] arystrchunkdata(string strdata) { int intchunks = (strdata.length() / chunk_threshold_bytesize) + 1; string[] arystrchunks = new string[intchunks]; int intlength = strdata.length(), intpos = 0; for( int c=0; c<arystrchunks.length; c++ ) { if ( intpos < intlength ) { //extract chunk data int intend = math.min(intlength, intpos + chunk_threshold_bytesize); arystrchunks[c] = strdata.substring(intpos, intend); intpos = intend; } } return arystrchunks; }
loop write chunks, no lengths @ beginning , no 0 byte @ end of chunks required:
string[] arystrchunks = arystrchunkdata(strresponse); for( string strchunk : arystrchunks ) { if ( strchunk != null ) { out.write(strchunk.getbytes()); } }
Comments
Post a Comment