Java Jersey 2.0 内容长度未设置

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18157218/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 22:53:24  来源:igfitidea点击:

Jersey 2.0 Content-Length not set

javajerseyjax-rsmoxy

提问by Todd

I'm trying to post to a web service that requires the Content-Length header to be set using the following code:

我正在尝试发布到需要使用以下代码设置 Content-Length 标头的 Web 服务:

// EDIT: added apache connector code
ClientConfig clientConfig = new ClientConfig();
ApacheConnector apache = new ApacheConnector(clientConfig);

// setup client to log requests and responses and their entities
client.register(new LoggingFilter(Logger.getLogger("com.example.app"), true));

Part part = new Part("123");
WebTarget target = client.target("https://api.thing.com/v1.0/thing/{thingId}");
Response jsonResponse = target.resolveTemplate("thingId", "abcdefg")
                .request(MediaType.APPLICATION_JSON)
                .header(HttpHeaders.AUTHORIZATION, "anauthcodehere")
                .post(Entity.json(part));

From the release notes https://java.net/jira/browse/JERSEY-1617and the Jersey 2.0 documentation https://jersey.java.net/documentation/latest/message-body-workers.htmlit implies that Content-Length is automatically set. However, I get a 411 response code back from the server indicating that Content-Length is not present in the request.

从发行说明https://java.net/jira/browse/JERSEY-1617和 Jersey 2.0 文档https://jersey.java.net/documentation/latest/message-body-workers.html它暗示 Content-长度自动设置。但是,我从服务器收到了一个 411 响应代码,表明请求中不存在 Content-Length。

Does anyone know the best way to get the Content-Length header set?

有谁知道获取 Content-Length 标头集的最佳方法?

I've verified through setting up a logger that the Content-Length header is not generated in the request.

我已经通过设置记录器验证了请求中未生成 Content-Length 标头。

Thanks.

谢谢。

回答by Christian Trimble

I ran a quick test with Jersey Client 2.2 and Netcat, and it is showing me that Jersey is sending the Content-Length header, even though the LoggingFilter is not reporting it.

我用 Jersey Client 2.2 和 Netcat 进行了一个快速测试,它显示 Jersey 正在发送 Content-Length 标头,即使 LoggingFilter 没有报告它。

To do this test, I first ran netcat in one shell.

为了做这个测试,我首先在一个 shell 中运行 netcat。

nc -l 8090

Then I executed the following Jersey code in another shell.

然后我在另一个 shell 中执行了以下 Jersey 代码。

Response response = ClientBuilder.newClient()
    .register(new LoggingFilter(Logger.getLogger("com.example.app"), true))
    .target("http://localhost:8090/test")
    .request()
    .post(Entity.json(IOUtils.toInputStream("{key:\"value\"}")));

After running this code, the following lines get logged.

运行此代码后,将记录以下行。

INFO: 1 * LoggingFilter - Request received on thread main
1 > POST http://localhost:8090/test
1 > Content-Type: application/json
{key:"value"}

However, netcat reports several more headers in the message.

但是,netcat 在消息中报告了更多的标头。

POST /test HTTP/1.1
Content-Type: application/json
User-Agent: Jersey/2.0 (HttpUrlConnection 1.7.0_17)
Host: localhost:8090
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 13

{key:"value"}

I ran this test on OSX with Java6 and Java7, with the same results. I also ran the test in Jersey 2.0, with similar results.

我使用 Java6 和 Java7 在 OSX 上运行了这个测试,结果相同。我也在 Jersey 2.0 中进行了测试,结果相似。

回答by Todd

After looking at the source code for the ApacheConnector class, I see the problem. When a ClientRequest is converted to a HttpUriRequest a private method getHttpEntity()is called that returns a HttpEntity. Unfortunately, this returns a HttpEntity whose getContentLength()always returns a -1.

查看 ApacheConnector 类的源代码后,我看到了问题。当 ClientRequest 转换为 HttpUriRequest 时,getHttpEntity()会调用一个返回HttpEntity 的私有方法。不幸的是,这将返回一个getContentLength()始终返回 -1的 HttpEntity 。

When the Apache http client creates the request it will consult the HttpEntity object for a length and since it returns -1 no Content-Lengthheader will be set.

当 Apache http 客户端创建请求时,它将查询 HttpEntity 对象的长度,并且由于它返回 -1,因此不会Content-Length设置标头。

I solved my problem by creating a new connector that is a copy of the source code for the ApacheConnector but has a different implementation of the getHttpEntity(). I read the entity from the original ClientRequestinto a byte array and then wrap that byte array with a ByteArrayEntity. When the Apache Http client creates the request it will consult the entity and the ByteArrayEntitywill respond with the correct content length which in turns allows the Content-Lengthheader to be set.

我通过创建一个新连接器解决了我的问题,该连接器是 ApacheConnector 源代码的副本,但具有不同的getHttpEntity(). 我将原始实体中的实体读ClientRequest入一个字节数组,然后用ByteArrayEntity. 当 Apache Http 客户端创建请求时,它将咨询实体,并ByteArrayEntity以正确的内容长度进行响应,从而允许设置Content-Length标头。

Here's the relevant code:

这是相关的代码:

private HttpEntity getHttpEntity(final ClientRequest clientRequest) {
    final Object entity = clientRequest.getEntity();

    if (entity == null) {
        return null;
    }

    byte[] content = getEntityContent(clientRequest);

    return new ByteArrayEntity(content);
}


private byte[] getEntityContent(final ClientRequest clientRequest) {

   // buffer into which entity will be serialized
   final ByteArrayOutputStream baos = new ByteArrayOutputStream();

   // set up a mock output stream to capture the output
   clientRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() {

        @Override
        public OutputStream getOutputStream(int contentLength) throws IOException {
            return baos;
        }
    });

    try {
        clientRequest.writeEntity();
    } 
    catch (IOException e) {
        LOGGER.log(Level.SEVERE, null, e);
        // re-throw new exception
        throw new ProcessingException(e);
    }

    return baos.toByteArray();
}

WARNING:My problem space was constrained and only contained small entity bodies as part of requests. This method proposed above may be problematic with large entity bodies such as images so I don't think this is a general solution for all.

警告:我的问题空间受到限制,仅包含作为请求一部分的小型实体。上面提出的这种方法对于大型实体(例如图像)可能会出现问题,因此我认为这不是所有人的通用解决方案。

回答by Arul Dhesiaseelan

This is supported in Jersey 2.5 (https://java.net/jira/browse/JERSEY-2224). You could use https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/client/RequestEntityProcessing.html#BUFFEREDto stream your content. I put together a simple example that shows both chunked and buffering content using ApacheConnector. Checkout this project: https://github.com/aruld/sof-18157218

这在 Jersey 2.5 ( https://java.net/jira/browse/JERSEY-2224) 中得到支持。您可以使用https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/client/RequestEntityProcessing.html#BUFFERED来流式传输您的内容。我整理了一个简单的例子,展示了使用 ApacheConnector 分块和缓冲的内容。签出这个项目:https: //github.com/aruld/sof-18157218

public class EntityStreamingTest extends JerseyTest {

  private static final Logger LOGGER = Logger.getLogger(EntityStreamingTest.class.getName());

  @Path("/test")
  public static class HttpMethodResource {
    @POST
    @Path("chunked")
    public String postChunked(@HeaderParam("Transfer-Encoding") String transferEncoding, String entity) {
      assertEquals("POST", entity);
      assertEquals("chunked", transferEncoding);
      return entity;
    }

    @POST
    public String postBuffering(@HeaderParam("Content-Length") String contentLength, String entity) {
      assertEquals("POST", entity);
      assertEquals(entity.length(), Integer.parseInt(contentLength));
      return entity;
    }
  }

  @Override
  protected Application configure() {
    ResourceConfig config = new ResourceConfig(HttpMethodResource.class);
    config.register(new LoggingFilter(LOGGER, true));
    return config;
  }

  @Override
  protected void configureClient(ClientConfig config) {
    config.connectorProvider(new ApacheConnectorProvider());
  }

  @Test
  public void testPostChunked() {
    Response response = target().path("test/chunked").request().post(Entity.text("POST"));

    assertEquals(200, response.getStatus());
    assertTrue(response.hasEntity());
  }

  @Test
  public void testPostBuffering() {
    ClientConfig cc = new ClientConfig();
    cc.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);
    cc.connectorProvider(new ApacheConnectorProvider());
    JerseyClient client = JerseyClientBuilder.createClient(cc);
    WebTarget target = client.target(getBaseUri());
    Response response = target.path("test").request().post(Entity.text("POST"));

    assertEquals(200, response.getStatus());
    assertTrue(response.hasEntity());
  }
}

回答by stivlo

I've tested with Jersey 2.25.1 a simpler solution that consists in setting setChunkedEncodingEnabled(false)in the Jersey Client configuration. Instead of using a chunked encoding, the whole entity is serialised in memory and the Content-Length is set on the request.

我已经用 Jersey 2.25.1 测试了一个更简单的解决方案,它包括在setChunkedEncodingEnabled(false)Jersey 客户端配置中进行设置。整个实体不是使用分块编码,而是在内存中序列化,并在请求中设置 Content-Length。

For reference, here is an example of a configuration I've used:

作为参考,这是我使用的配置示例:

private Client createJerseyClient(Environment environment) {
    Logger logger = Logger.getLogger(getClass().getName());
    JerseyClientConfiguration clientConfig = new JerseyClientConfiguration();
    clientConfig.setProxyConfiguration(new ProxyConfiguration("localhost", 3333));
    clientConfig.setGzipEnabled(false);
    clientConfig.setGzipEnabledForRequests(false);
    clientConfig.setChunkedEncodingEnabled(false);
    return new JerseyClientBuilder(environment)
            .using(clientConfig)
            .build("RestClient")
            .register(new LoggingFeature(logger, Level.INFO, null, null));
}

I've used mitmproxyto verify the request headers and the Content-Lengthheader was set correctly.

我已经使用mitmproxy来验证请求标头并且Content-Length标头设置正确。

回答by shero yu

@Test
public void testForbiddenHeadersAllowed() {
    Client client = ClientBuilder.newClient();
    System.setProperty("sun.net.http.allowRestrictedHeaders", "true");

    Response response = testHeaders(client);
    System.out.println(response.readEntity(String.class));
    Assert.assertEquals(200, response.getStatus());