2013-08-09 14 views
13

Sto cercando di inviare a un servizio Web che richiede l'intestazione Content-Length essere impostato utilizzando il seguente codice:Jersey 2,0 Content-Length non impostato

// 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)); 

Dalla note di rilascio https://java.net/jira/browse/JERSEY-1617 e la Jersey 2.0 documentazione https://jersey.java.net/documentation/latest/message-body-workers.html implica che Content-Length è impostato automaticamente. Tuttavia, ricevo un codice di risposta 411 dal server che indica che Content-Length non è presente nella richiesta.

Qualcuno conosce il modo migliore per ottenere l'intestazione Content-Length impostata?

Ho verificato attraverso l'impostazione di un logger che l'intestazione Content-Length non viene generata nella richiesta.

Grazie.

+0

È possibile verificare se il contenuto della richiesta ha o meno la lunghezza del contenuto. –

+1

Attivare la registrazione per verificare la richiesta ('client.addFilter (new LoggingFilter (System.out))') in questo modo si può essere sicuri che il problema sia da parte vostra. – DannyMo

+0

Ho trascurato di dire che ho creato un logger e verificato che l'intestazione Content-Length non è stata generata. Ho modificato la mia domanda per riflettere le nuove informazioni. – Todd

risposta

5

Ho eseguito un test rapido con Jersey Client 2.2 e Netcat e mi mostra che Jersey sta inviando l'intestazione Content-Length, anche se il LoggingFilter non lo segnala.

Per eseguire questo test, ho eseguito prima netcat in una shell.

nc -l 8090 

Quindi ho eseguito il seguente codice Jersey in un'altra shell.

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\"}"))); 

Dopo aver eseguito questo codice, vengono registrate le seguenti righe.

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

Tuttavia, netcat segnala numerose altre intestazioni nel messaggio.

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"} 

Ho eseguito questo test su OSX con Java6 e Java7, con gli stessi risultati. Ho anche eseguito il test in Jersey 2.0, con risultati simili.

+0

Cercherò di verificare questo oggi e quindi assegnerò la taglia. Non sembra che questo spieghi perché l'OP stava ottenendo i 411, ma soddisfa la mia stessa curiosità. Non pensavo di controllare se la lunghezza del contenuto veniva aggiunta dopo il filtro di registrazione. Grazie! – DannyMo

+0

Jersey recupera questa funzionalità dall'oggetto JavaUrlConnection di Java, almeno nel mio ambiente. L'OP può avere un altro client HTTP configurato che utilizza HTTP/1.0, dove non sono richieste intestazioni Content-Length (o chunking). –

+0

Ho fatto il passaggio a ApacheConnector per risolvere un problema con il connettore predefinito generando un'eccezione su un'intestazione WWW-Authenticate che funziona perfettamente con il client Http Apache. Ho trovato il problema con ApacheConnector e ho finito di scrivere il mio connettore che consente al client Apache Http di impostare correttamente il Content-Length. – Todd

3

Dopo aver esaminato il codice sorgente per la classe ApacheConnector, vedo il problema. Quando ClientRequest viene convertito in HttpUriRequest, viene chiamato un metodo privato getHttpEntity() che restituisce un HttpEntity. Sfortunatamente, questo restituisce un HttpEntity di cui getContentLength() restituisce sempre un -1.

Quando il client http Apache crea la richiesta, consulta l'oggetto HttpEntity per una lunghezza e poiché restituisce -1 non viene impostata l'intestazione Content-Length.

Ho risolto il problema creando un nuovo connettore che è una copia del codice sorgente per ApacheConnector ma ha un'implementazione diversa dello getHttpEntity(). Ho letto l'entità dall'originale ClientRequest in una matrice di byte e quindi avvolgo quella matrice di byte con una ByteArrayEntity. Quando il client Apache Http crea la richiesta, consulta l'entità e lo ByteArrayEntity risponderà con la lunghezza del contenuto corretta che a sua volta consente di impostare l'intestazione Content-Length.

Ecco il codice rilevante:

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

ATTENZIONE: My space problema è stato limitato e conteneva solo piccoli corpi di entità, come parte delle richieste.Questo metodo proposto sopra può essere problematico con corpi di grandi entità come le immagini, quindi non penso che questa sia una soluzione generale per tutti.

3

Questo è supportato in Jersey 2.5 (https://java.net/jira/browse/JERSEY-2224). È possibile utilizzare https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/client/RequestEntityProcessing.html#BUFFERED per lo streaming dei contenuti. Ho creato un semplice esempio che mostra sia il contenuto chunked che il buffering usando ApacheConnector. Checkout questo progetto: 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

Ho provato con Jersey 2.25.1 una soluzione più semplice che consiste nella creazione di setChunkedEncodingEnabled(false) nella configurazione del client Jersey. Invece di usare una codifica chunked, l'intera entità viene serializzata in memoria e il Content-Length viene impostato sulla richiesta.

Per riferimento, ecco un esempio di una configurazione che ho usato:

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

ho usato mitmproxy per verificare le intestazioni di richiesta e l'intestazione Content-Length è stato impostato correttamente.

Problemi correlati