我正在尝试发布到需要使用以下代码设置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));
从发行说明https://java.net/jira/browse/JERSEY-1617和Jersey 2.0文档https://jersey.java.net/documentation/latest/message-body-workers.html开始,它意味着自动设置Content-Length。但是,我从服务器返回411响应代码,表明请求中没有Content-Length。
有没有人知道设置Content-Length标头的最佳方法?
我已经通过设置记录器验证了请求中没有生成Content-Length标头。
感谢。
答案 0 :(得分:5)
我使用Jersey Client 2.2和Netcat进行了快速测试,它向我显示Jersey正在发送Content-Length标头,即使LoggingFilter没有报告它。
为了做这个测试,我首先在一个shell中运行netcat。
nc -l 8090
然后我在另一个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\"}")));
运行此代码后,将记录以下行。
INFO: 1 * LoggingFilter - Request received on thread main
1 > POST http://localhost:8090/test
1 > Content-Type: application/json
{key:"value"}
但是,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"}
我在OSX上用Java6和Java7运行了这个测试,结果相同。我也在Jersey 2.0中进行了测试,结果相似。
答案 1 :(得分:4)
在查看ApacheConnector类的源代码后,我看到了问题。当ClientRequest转换为HttpUriRequest时,将调用一个返回HttpEntity的私有方法getHttpEntity()
。不幸的是,这会返回一个HttpEntity,其getContentLength()
始终返回-1。
当Apache http客户端创建请求时,它会查询HttpEntity对象的长度,因为它返回-1,所以不会设置Content-Length
标头。
我通过创建一个新连接器解决了我的问题,该连接器是ApacheConnector源代码的副本,但具有getHttpEntity()
的不同实现。我将原始ClientRequest
中的实体读入字节数组,然后用ByteArrayEntity
包装该字节数组。当Apache Http客户端创建请求时,它将咨询实体,ByteArrayEntity
将使用正确的内容长度进行响应,从而允许设置Content-Length
标头。
以下是相关代码:
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();
}
警告:我的问题空间受到限制,并且只包含小型实体主体作为请求的一部分。上面提出的这种方法对于像图像这样的大型实体主体可能存在问题,因此我认为这不是所有人的通用解决方案。
答案 2 :(得分:3)
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显示了chunked和buffering内容。查看此项目: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());
}
}
答案 3 :(得分:3)
我已经使用Jersey 2.25.1测试了一个更简单的解决方案,该解决方案包括在Jersey客户端配置中设置setChunkedEncodingEnabled(false)
。整个实体不是使用分块编码,而是在内存中序列化,并在请求中设置Content-Length。
供参考,以下是我使用的配置示例:
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));
}
我已使用mitmproxy验证请求标头并正确设置了Content-Length
标头。
答案 4 :(得分:0)
@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());