Soap 带有大附件的HttpEntity的SWA响应的Java传输失败(can';t块多部分)

Soap 带有大附件的HttpEntity的SWA响应的Java传输失败(can';t块多部分),soap,server,multipart,apache-httpcomponents,chunked-encoding,Soap,Server,Multipart,Apache Httpcomponents,Chunked Encoding,请注意:此问题也已发布到官方的Apache HttpClient用户邮件列表中 问题 基于ApacheHttpComponents构建的基于Java的服务器应该如何组装和发送带有附件的大型SOAP(SWA)响应 问题详细信息 Apache HttpClient Java API不支持分块多部分HttpEntity实例。当SWA响应中的附件较大(>2 MiB)时,向模拟服务器发出SOAP请求的客户端失败(具体来说,基于Apache的Java客户端抛出org.Apache.http.NoHttpRe

请注意:此问题也已发布到官方的Apache HttpClient用户邮件列表中

问题

基于ApacheHttpComponents构建的基于Java的服务器应该如何组装和发送带有附件的大型SOAP(SWA)响应

问题详细信息

Apache HttpClient Java API不支持分块多部分HttpEntity实例。当SWA响应中的附件较大(>2 MiB)时,向模拟服务器发出SOAP请求的客户端失败(具体来说,基于Apache的Java客户端抛出org.Apache.http.NoHttpResponseException)。传输小附件时不会引发任何异常。HTTP内容长度头是正确的

用例

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.ExceptionLogger;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import static org.apache.http.HttpStatus.SC_OK;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.nio.bootstrap.HttpServer;
import org.apache.http.impl.nio.bootstrap.ServerBootstrap;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.protocol.BasicAsyncRequestConsumer;
import org.apache.http.nio.protocol.BasicAsyncResponseProducer;
import org.apache.http.nio.protocol.HttpAsyncExchange;
import org.apache.http.nio.protocol.HttpAsyncRequestConsumer;
import org.apache.http.nio.protocol.HttpAsyncRequestHandler;
import org.apache.http.protocol.HttpContext;

public class HttpComponentsConciseTest {

    /* 
     * The random.png.gz file must be large (> 2 MiB)
     */
    private static final String LARGE_COMPRESSED_FILE_NAME = "random.png.gz";
    private static final File LARGE_TEST_GZIP_FILE = new File(LARGE_COMPRESSED_FILE_NAME);
    private static final String SOAP_RESPONSE_MESSAGE_XML
            = "<soapenv:Envelope "
            + "xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
            + "   <soapenv:Header/>\n"
            + "   <soapenv:Body>\n"
            + "      <AResponse>\n"
            + "         <ResponseFile>" + LARGE_COMPRESSED_FILE_NAME + "</ResponseFile>\n"
            + "      </AResponse>\n"
            + "   </soapenv:Body>\n"
            + "</soapenv:Envelope>";

    private static final int PORT_NUMBER = 8080;

    public static void main(String[] args) {
        startHttpServer();
    }

    private static void startHttpServer() {
        IOReactorConfig config = IOReactorConfig.custom()
                .setIoThreadCount(1)
                .setSoTimeout(15000)
                .setTcpNoDelay(true)
                .build();

        final HttpServer server = ServerBootstrap.bootstrap()
                .setListenerPort(PORT_NUMBER)
                .setServerInfo("Test/1.1")
                .setIOReactorConfig(config)
                .setExceptionLogger(ExceptionLogger.STD_ERR)
                .registerHandler("*", new PrimaryRequestHandler())
                .create();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> server.shutdown(5,
                TimeUnit.MILLISECONDS)));

        Thread serverOwner = new Thread(() -> {

            try {
                server.start();
            } catch (IOException ex) {
                Logger.getLogger(HttpComponentsConciseTest.class.getName()).log(Level.SEVERE, null,
                        ex);
            }
        }, "ServerOwner");
        serverOwner.start();
    }

    private static class PrimaryRequestHandler implements HttpAsyncRequestHandler<HttpRequest> {

        @Override
        public HttpAsyncRequestConsumer<HttpRequest> processRequest(
                final HttpRequest request,
                final HttpContext context) {
            return new BasicAsyncRequestConsumer();
        }

        @Override
        public void handle(
                final HttpRequest request,
                final HttpAsyncExchange httpexchange,
                final HttpContext context) throws HttpException, IOException {
            HttpResponse response = httpexchange.getResponse();

            String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
            if (!method.equals("POST")) {
                throw new MethodNotSupportedException(method + " method not supported");
            }

            StringBody soapResponseStringBody = new StringBody(SOAP_RESPONSE_MESSAGE_XML,
                    ContentType.APPLICATION_XML);
            FileBody soapAttachment = new FileBody(LARGE_TEST_GZIP_FILE);
            HttpEntity responseEntity = MultipartEntityBuilder.create()
                    .addPart("SOAP Envelope", soapResponseStringBody)
                    .addPart(LARGE_COMPRESSED_FILE_NAME, soapAttachment)
                    .build();
            response.setStatusCode(SC_OK);
            response.setEntity(responseEntity);

            httpexchange.submitResponse(new BasicAsyncResponseProducer(response));
        }
    }
}
能够传输任意内容长度的SWA响应的多线程SOAP服务器

平台

Java 8 64b

必需的库

HttpCore v4.4.6+HttpTime v4.5.3

错误复制

  • 使用HttpCore v4.4.6和HttpMime v4.5.3库构建示例源代码(httpime是HttpClient项目的一部分)
  • 使用足够大的(>2 MiB)“random.png.gz”二进制文件运行程序
  • 通过某个第三方客户端向服务器发送任意HTTP POST请求。由于它是一个基于Apache的Java客户端,具有精确日志记录功能,因此建议使用SoapUINB:无法接收大型服务器生成的多部分结果
  • 示例服务器源

    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.util.Locale;
    import java.util.concurrent.TimeUnit;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import org.apache.http.ExceptionLogger;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpException;
    import org.apache.http.HttpRequest;
    import org.apache.http.HttpResponse;
    import static org.apache.http.HttpStatus.SC_OK;
    import org.apache.http.MethodNotSupportedException;
    import org.apache.http.entity.ContentType;
    import org.apache.http.entity.mime.MultipartEntityBuilder;
    import org.apache.http.entity.mime.content.ByteArrayBody;
    import org.apache.http.entity.mime.content.StringBody;
    import org.apache.http.impl.nio.bootstrap.HttpServer;
    import org.apache.http.impl.nio.bootstrap.ServerBootstrap;
    import org.apache.http.impl.nio.reactor.IOReactorConfig;
    import org.apache.http.nio.protocol.BasicAsyncRequestConsumer;
    import org.apache.http.nio.protocol.BasicAsyncResponseProducer;
    import org.apache.http.nio.protocol.HttpAsyncExchange;
    import org.apache.http.nio.protocol.HttpAsyncRequestConsumer;
    import org.apache.http.nio.protocol.HttpAsyncRequestHandler;
    import org.apache.http.protocol.HttpContext;
    
    public class HttpComponentsConciseTest {
    
        /* 
         * The random.png.gz file must be large (> 2 MiB)
         */
        private static final String LARGE_COMPRESSED_FILE_NAME = "random.png.gz";
        private static final File LARGE_TEST_GZIP_FILE = new File(LARGE_COMPRESSED_FILE_NAME);
        private static final String SOAP_RESPONSE_MESSAGE_XML
                = "<soapenv:Envelope "
                + "xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
                + "   <soapenv:Header/>\n"
                + "   <soapenv:Body>\n"
                + "      <AResponse>\n"
                + "         <ResponseFile>" + LARGE_COMPRESSED_FILE_NAME + "</ResponseFile>\n"
                + "      </AResponse>\n"
                + "   </soapenv:Body>\n"
                + "</soapenv:Envelope>";
    
        private static final int PORT_NUMBER = 8080;
    
        public static void main(String[] args) {
            startHttpServer();
        }
    
        private static void startHttpServer() {
            IOReactorConfig config = IOReactorConfig.custom()
                    .setIoThreadCount(1)
                    .setSoTimeout(15000)
                    .setTcpNoDelay(true)
                    .build();
    
            final HttpServer server = ServerBootstrap.bootstrap()
                    .setListenerPort(PORT_NUMBER)
                    .setServerInfo("Test/1.1")
                    .setIOReactorConfig(config)
                    .setExceptionLogger(ExceptionLogger.STD_ERR)
                    .registerHandler("*", new PrimaryRequestHandler())
                    .create();
    
            Runtime.getRuntime().addShutdownHook(new Thread(() -> server.shutdown(5,
                    TimeUnit.MILLISECONDS)));
    
            Thread serverOwner = new Thread(() -> {
    
                try {
                    server.start();
                } catch (IOException ex) {
                    Logger.getLogger(HttpComponentsConciseTest.class.getName()).log(Level.SEVERE, null,
                            ex);
                }
            }, "ServerOwner");
            serverOwner.start();
        }
    
        private static class PrimaryRequestHandler implements HttpAsyncRequestHandler<HttpRequest> {
    
            @Override
            public HttpAsyncRequestConsumer<HttpRequest> processRequest(
                    final HttpRequest request,
                    final HttpContext context) {
                return new BasicAsyncRequestConsumer();
            }
    
            @Override
            public void handle(
                    final HttpRequest request,
                    final HttpAsyncExchange httpexchange,
                    final HttpContext context) throws HttpException, IOException {
                HttpResponse response = httpexchange.getResponse();
    
                String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
                if (!method.equals("POST")) {
                    throw new MethodNotSupportedException(method + " method not supported");
                }
    
                StringBody soapResponseStringBody = new StringBody(SOAP_RESPONSE_MESSAGE_XML,
                        ContentType.APPLICATION_XML);
                FileBody soapAttachment = new FileBody(LARGE_TEST_GZIP_FILE);
                HttpEntity responseEntity = MultipartEntityBuilder.create()
                        .addPart("SOAP Envelope", soapResponseStringBody)
                        .addPart(LARGE_COMPRESSED_FILE_NAME, soapAttachment)
                        .build();
                response.setStatusCode(SC_OK);
                response.setEntity(responseEntity);
    
                httpexchange.submitResponse(new BasicAsyncResponseProducer(response));
            }
        }
    }
    
    导入java.io.File;
    导入java.io.IOException;
    导入java.nio.file.Files;
    导入java.util.Locale;
    导入java.util.concurrent.TimeUnit;
    导入java.util.logging.Level;
    导入java.util.logging.Logger;
    导入org.apache.http.ExceptionLogger;
    导入org.apache.http.HttpEntity;
    导入org.apache.http.HttpException;
    导入org.apache.http.HttpRequest;
    导入org.apache.http.HttpResponse;
    导入静态org.apache.http.HttpStatus.SC\u OK;
    导入org.apache.http.MethodNotSupportedException;
    导入org.apache.http.entity.ContentType;
    导入org.apache.http.entity.mime.MultipartEntityBuilder;
    导入org.apache.http.entity.mime.content.ByteArrayBody;
    导入org.apache.http.entity.mime.content.StringBody;
    导入org.apache.http.impl.nio.bootstrap.HttpServer;
    导入org.apache.http.impl.nio.bootstrap.ServerBootstrap;
    导入org.apache.http.impl.nio.reactor.IOReactorConfig;
    导入org.apache.http.nio.protocol.BasicAsyncRequestConsumer;
    导入org.apache.http.nio.protocol.BasicAsyncResponseProducer;
    导入org.apache.http.nio.protocol.HttpAsyncExchange;
    导入org.apache.http.nio.protocol.HttpAsyncRequestConsumer;
    导入org.apache.http.nio.protocol.HttpAsyncRequestHandler;
    导入org.apache.http.protocol.HttpContext;
    公共类HttpComponents协同测试{
    /* 
    *random.png.gz文件必须很大(>2 MiB)
    */
    私有静态最终字符串大\u压缩\u文件\u NAME=“random.png.gz”;
    私有静态最终文件大\u测试\u GZIP\u文件=新文件(大\u压缩\u文件名);
    私有静态最终字符串SOAP\u响应\u消息\u XML
    =“\n”
    +“\n”
    +“\n”
    +"