我正在开发一个具有内置HTTP服务器的Java应用程序,当服务器使用ServerSocketChannel实现时,它会在端口1694上侦听请求:
msvrCh = ServerSocketChannel.open();
msvrCh.socket().bind(new InetSocketAddress(mintPort));
msvrCh.configureBlocking(false);
安装了一个线程来管理请求和响应:
Thread thrd = new Thread(msgReceiver);
thrd.setUncaughtExceptionHandler(exceptionHandler);
thrd.start();
线程非常简单:
Runnable msgReceiver = new Runnable() {
@Override
public void run() {
try{
while( !Thread.interrupted() ) {
//Sleep a short period between checks for 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();
} finally {
logMsg(TERMINATING_THREAD +
"for accepting cluster connections", true);
if ( msvrCh != null ) {
try {
msvrCh.close();
} catch (IOException ex) {
ex.printStackTrace();
}
msvrCh = null;
}
}
}
};
处理响应的主要代码是函数processRequest:
private void processRequest(Socket sck) {
try {
//AJAX Parameters
final String AJAX_ID = "ajmid";
//The 'Handler Key' used to 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 the end of the 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 the 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 is being used by this request?
if ( strRequest.startsWith(HTTP_GET) ) {
//The request should end with a HTTP marker, remove this before trying to interpret the data
if ( strRequest.indexOf(HTTP_MARKER) != -1 ) {
strRequest = strRequest.substring(0, strRequest.indexOf(HTTP_MARKER)).trim();
}
//Look for a data marker
if ( (intPos = strRequest.indexOf(HTTP_DATA_START)) >= 0 ) {
//Data is present in the query, skip to the start of the data
strRequest = strRequest.substring(intPos + 1);
} else {
//Remove the method indicator
strRequest = strRequest.substring(HTTP_GET.length());
}
} else if ( strRequest.startsWith(HTTP_POST) ) {
//Discard the headers and jump to the data
if ( (intPos = strRequest.lastIndexOf(CRLF)) >= 0 ) {
strRequest = strRequest.substring(intPos + CRLF.length());
}
}
if ( strRequest.length() > 1 ) {
//Extract the parameters
objParams = new clsHTTPparameters(strRequest);
}
if ( strRequest.startsWith("/") == true ) {
//Look for the document reference
strRequest = strRequest.substring(1);
eType = eMsgTypes.SEND_DOC;
}
if ( objParams != null ) {
//Transfer the payload to the 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 is JSON, is there a type parameter?
String strType = strGetJSONItem(strRequest, JSON_LBL_TYPE);
if ( strType != null && strType.length() > 0 ) {
//Decode the type
eType = eMsgTypes.valueOf(strType.toUpperCase().trim());
//What system is the 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 this system known in the cluster?
clsIPmon objSystem = objAddSysToCluster(strIP, strMAC);
if ( objSystem != null ) {
//Update the date/time stamp of the remote system
objSystem.touch();
}
//This is an 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 to restart system
strRespPayload = "Shutdown in progress!";
strContentType = MIME_PLAIN;
//Flag that shutdown has been requested
blnShutdown = true;
break;
default:
}
if ( strRespPayload != null ) {
//Convert response string to 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 the request AJAX Message ID in the 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 is not the local system, issue message to remote system.
broadcastMessage("{\"" + JSON_LBL_TYPE + "\":\"" +
eMsgTypes.SHUTDOWN + "\""
+ ",\"" + JSON_LBL_TIME + "\":\"" +
clsTimeMan.lngTimeNow() + "\"}");
} else {
//Shutdown addressed to 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) {
} finally {
if (sck != null) {
try {
sck.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
我想实现一个分块响应,目前上面的代码不支持分块响应。
[编辑]我试图通过添加方法来实现分块响应:
/**
* @param strData - The data to split into chunks
* @return A string array containing the 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 a chunk from the data
int intEnd = Math.min(intLength - 1, intPos + CHUNK_THRESHOLD_BYTESIZE);
arystrChunks[c] = strData.substring(intPos, intEnd);
}
//Advance data position to next chunk
intPos += CHUNK_THRESHOLD_BYTESIZE;
}
return arystrChunks;
}
修改后的processRequest现在看起来像这样:
private void processRequest(Socket sck) {
try {
//AJAX Parameters
final String AJAX_ID = "ajmid";
//The 'Handler Key' used to 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 the end of the 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 the 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 is being used by this request?
if ( strRequest.startsWith(HTTP_GET) ) {
//The request should end with a HTTP marker, remove this before trying to interpret the data
if ( strRequest.indexOf(HTTP_MARKER) != -1 ) {
strRequest = strRequest.substring(0, strRequest.indexOf(HTTP_MARKER)).trim();
}
//Look for a data marker
if ( (intPos = strRequest.indexOf(HTTP_DATA_START)) >= 0 ) {
//Data is present in the query, skip to the start of the data
strRequest = strRequest.substring(intPos + 1);
} else {
//Remove the method indicator
strRequest = strRequest.substring(HTTP_GET.length());
}
} else if ( strRequest.startsWith(HTTP_POST) ) {
//Discard the headers and jump to the data
if ( (intPos = strRequest.lastIndexOf(CRLF)) >= 0 ) {
strRequest = strRequest.substring(intPos + CRLF.length());
}
}
if ( strRequest.length() > 1 ) {
//Extract the parameters
objParams = new clsHTTPparameters(strRequest);
}
if ( strRequest.startsWith("/") == true ) {
//Look for the document reference
strRequest = strRequest.substring(1);
eType = eMsgTypes.SEND_DOC;
}
if ( objParams != null ) {
//Transfer the payload to the 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 is JSON, is there a type parameter?
String strType = strGetJSONItem(strRequest, JSON_LBL_TYPE);
if ( strType != null && strType.length() > 0 ) {
//Decode the type
eType = eMsgTypes.valueOf(strType.toUpperCase().trim());
//What system is the 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 this system known in the cluster?
clsIPmon objSystem = objAddSysToCluster(strIP, strMAC);
if ( objSystem != null ) {
//Update the date/time stamp of the remote system
objSystem.touch();
}
//This is an internal cluster message, no response required
return;
}
}
}
String strContentType = null, strRespPayload = null;
OutputStream out = sck.getOutputStream();
byte[] arybytResponse = null;
boolean blnShutdown = false;
//Start the writing the 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 to restart system
strRespPayload = "Shutdown in progress!";
strContentType = MIME_PLAIN;
//Flag that shutdown has been requested
blnShutdown = true;
break;
default:
}
if ( strRespPayload != null ) {
//Convert response string to 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 the request AJAX Message ID in the 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 is the response?
if ( strResponse.length() > CHUNK_THRESHOLD_BYTESIZE ) {
blnChunked = true;
strHeaders += "Transfer-Encoding: chunked\n\n";
out.write(strHeaders.getBytes());
//Slice up the string into 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 is always 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 is not the local system, issue message to remote system.
broadcastMessage("{\"" + JSON_LBL_TYPE + "\":\"" +
eMsgTypes.SHUTDOWN + "\""
+ ",\"" + JSON_LBL_TIME + "\":\"" +
clsTimeMan.lngTimeNow() + "\"}");
} else {
//Shutdown addressed to 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) {
} finally {
if (sck != null) {
try {
sck.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
我已经阅读了Chunked响应的几个规范,据我所知,我正在以正确的格式发送数据,但是我在浏览器中没有收到任何内容。
我可能错误地认为浏览器会将块正确地拼凑成一块,但我可能错了。客户端处理程序如下所示:
this.responseHandler = function() {
try {
if ( mobjHTTP == null
|| !(mobjHTTP.readyState == 4 && mobjHTTP.status == 200)
|| !(mstrResponseText = mobjHTTP.responseText)
|| mstrResponseText.length == 0 ) {
//Not ready or no response to decode
return;
}
//Do something with the response
} catch( ex ) {
T.error("responseHandler:", ex);
}
};
此处理程序在对象的其他位置设置:
mobjHTTP.onreadystatechange = this.responseHandler;
答案 0 :(得分:1)
解决了,不知道为什么,但删除标题:
Transfer-Encoding: chunked
此外,每个块开头的块长度解决了问题,我仍然以768字节块的形式写入数据。这非常有效。
不知道为什么我必须这样做。
从数据字符串生成块的最终方法:
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 a chunk from the data
int intEnd = Math.min(intLength, intPos + CHUNK_THRESHOLD_BYTESIZE);
arystrChunks[c] = strData.substring(intPos, intEnd);
intPos = intEnd;
}
}
return arystrChunks;
}
循环写入块,开头没有长度,并且在所需块的末尾没有0字节:
String[] arystrChunks = arystrChunkData(strResponse);
for( String strChunk : arystrChunks ) {
if ( strChunk != null ) {
out.write(strChunk.getBytes());
}
}
答案 1 :(得分:1)
正如我已经评论过的,HTTP响应大小没有官方限制。 TCP这对你有用。但是,您始终可以通过为现代浏览器设置Content-Length :: 32位整数最大大小或64位来配置Web服务器以实现此类策略(请参阅here)。
从技术上讲,您可以在帖子中使用Chunked Transfer获得无限响应。从理论上讲,这用于绕过最大内容长度。
最常见的是,如果对巨大的JSON文件(至少有一些MB大小)有这样的要求,您可以通过顺序AJAX请求使用某种分页逻辑。在您的情况下,您可以以编程方式将大JSON数据拆分为块,并通过另一个AJAX请求发送每个数据。然后,让Javascript执行合并任务。
通常,某些MB大小的JSON响应将在任何浏览器上成功加载。我建议你看一下this文章;这是3岁,但我想现在的情况甚至更好。
简而言之,上述基准测试表明,大小小于35 MB的JSON可能会在任何现代桌面浏览器上成功加载。但是,移动浏览器可能不是这种情况。例如,对于&gt; 10MB Json文件的移动版Safari限制有一些reports。