Java HTTP 服务器发送分块响应

Java HTTP server sending chunked response

我正在开发一个 Java 应用程序,它有一个内置的 HTTP 服务器,目前服务器是使用 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();
            }
        }
    }
}

我已经阅读了几个关于分块响应的规范,据我所知我正在以正确的格式发送数据,但是我在浏览器中没有收到任何东西。

我可能错误地认为浏览器会正确地将这些块拼凑成一个,但我可能是错的。客户端处理程序如下所示:

      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;

已解决,不知道为什么,但删除了 header:

  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());
            }                           
    }

正如我已经评论过的,HTTP 响应大小没有官方限制。 TCP 为您完成这项工作。但是,您始终可以通过为现代浏览器设置 Content-Length :: 32 位整数最大大小或 64 位来配置您的 Web 服务器以实施此类策略(请参阅 here)。

从技术上讲,您可以像您在 post 上声明的那样使用分块传输进行无限制的响应。理论上,这是用来绕过max Content-Length的。

最常见的是,如果对巨大的 JSON 文件有这样的要求(至少一些 MB),您可以通过顺序 AJAX 请求使用某种分页逻辑。在您的情况下,您可以将 JSON 大数据以编程方式拆分为块,然后通过另一个 AJAX 请求发送每个块。然后,让Javascript执行合并任务。

通常,大小为 MB 的 JSON 响应将在任何浏览器上成功加载。我建议你看看 this 文章;已经3岁了,但我想现在情况更好了。

简而言之,上述基准表明 JSON 小于 35 MB 的大小可能会在任何现代桌面浏览器上成功加载。然而,对于移动浏览器而言,情况可能并非如此。例如,对于 >10MB Json 文件的移动 Safari 有一些 reports 限制。

如果您传输用户 Transfer-Encoding=chunked 那么每个数据块之前必须有块的大小。

这里有一个很好的解释: https://en.wikipedia.org/wiki/Chunked_transfer_encoding