[英]Jersey 2.0 Content-Length not set
我正在嘗試發布到需要使用以下代碼設置 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它暗示內容-長度自動設置。 但是,我從服務器收到了一個 411 響應代碼,表明請求中不存在 Content-Length。
有誰知道獲取 Content-Length 標頭集的最佳方法?
我已經通過設置記錄器驗證了請求中未生成 Content-Length 標頭。
謝謝。
我用 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"}
我使用 Java6 和 Java7 在 OSX 上運行了這個測試,結果相同。 我也在 Jersey 2.0 中進行了測試,結果相似。
查看 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();
}
警告:我的問題空間受到限制,僅包含作為請求一部分的小型實體。 上面提出的這種方法對於大型實體(例如圖像)可能會出現問題,因此我認為這不是所有人的通用解決方案。
我已經用 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
標頭設置正確。
這在 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());
}
}
@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());
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.