I'm trying to post to a web service that requires the Content-Length header to be set using the following code:
// 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-1617 and the Jersey 2.0 documentation https://jersey.java.net/documentation/latest/message-body-workers.html it 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.
Does anyone know the best way to get the Content-Length header set?
I've verified through setting up a logger that the Content-Length header is not generated in the request.
Thanks.
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.
To do this test, I first ran netcat in one shell.
nc -l 8090
Then I executed the following Jersey code in another 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\"}")));
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.
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.
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.
When the Apache http client creates the request it will consult the HttpEntity object for a length and since it returns -1 no Content-Length header will be set.
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 ClientRequest into 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 ByteArrayEntity will respond with the correct content length which in turns allows the Content-Length header to be set.
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.
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.
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 mitmproxy to verify the request headers and the Content-Length header was set correctly.
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#BUFFERED to 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
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());
Related
I'd like to send a Content-Length header in an HTTP request using Jersey client 1.18 over Dropwizard.
LoggingFilter shows that the request does not contain Content-Length and the OP says it doesn't receive it.
However, when I try to add it manually, I get an org.apache.http.ProtocolException: Content-Length header already present error.
Here's my code:
byte[] entity = objectMapper.writeValueAsBytes(msg);
response = client.resource(uri)
.type(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.header(TransactionIdUtils.TRANSACTION_ID_HEADER, transactionId)
.header(CREDENTIALS_HEADER, this.credentials)
.header(HttpHeaders.CONTENT_LENGTH, entity.length)
.post(ClientResponse.class, entity);
Any advice is much appreciated.
Edit: Here's my LoggingFilter code:
client.addFilter(new LoggingFilter(new JulFacade()));
//a class for redirecting Jersey logging to sl4j
private static class JulFacade extends java.util.logging.Logger {
JulFacade() {
super("Jersey", null);
}
#Override
public void info(String msg) {
LOGGER.info(msg);
}
}
I need to communicate via SOAP with an application that was bought from another company. The wsdl we received is for SOAP 1.2 and the same is stated in the minimalistic technical documentation. However every request produces the following error:
Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'. [415]
According to the supplier their software was reprogrammed to use SOAP 1.2 with the content-type from SOAP 1.1 (text/xml; charset=utf-8). I'm using Spring Boot with the WebServiceGatewaySupport. The SoapMessageFactory is configured to use SOAP 1.2 which (as expected) sets the content-type to 'application/soap+xml; charset=utf-8':
#Bean(name = "mySoapMessageFactory")
public SaajSoapMessageFactory saajSoapMessageFactory() throws SOAPException {
SaajSoapMessageFactory saajSoapMessageFactory = new SaajSoapMessageFactory(MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL));
saajSoapMessageFactory.setSoapVersion(SoapVersion.SOAP_12);
return saajSoapMessageFactory;
}
In the long run the company which supplied the SOAP endpoint should fix their software to honor the standards. But until then I am looking for a quick fix since the data needs to be tested...
What I tried so far:
1) Change the content-type header with a WebServiceMessageCallback. Results in the same error as the content-type seems to be switched back to the SOAP 1.2 default before the request is sent.
public GetOrganisationUnitDataResponse getOrganisationUnitDataResponse(Integer skip) {
//SNIP
GetOrganisationUnitDataResponse response = (GetOrganisationUnitDataResponse) getWebServiceTemplate().marshalSendAndReceive(getOrganisationUnitData, getWebServiceMessageCallback());
//SNIP
return response;
}
private WebServiceMessageCallback getWebServiceMessageCallback() {
return webServiceMessage -> {
SaajSoapMessage soapMessage = (SaajSoapMessage)webServiceMessage;
MimeHeaders headers = soapMessage.getSaajMessage().getMimeHeaders();
headers.setHeader(TransportConstants.HEADER_CONTENT_TYPE, "text/xml; charset=utf-8");
headers.setHeader("Accept", "text/xml; charset=utf-8");
};
}
2) ClientInterceptor modifying the SaajSoapMessage. Same modification as in point 1) with the same result.
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
WebServiceMessage request = messageContext.getRequest();
SaajSoapMessage soapMessage = (SaajSoapMessage)request;
MimeHeaders headers = soapMessage.getSaajMessage().getMimeHeaders();
headers.removeHeader("Content-Type");
headers.setHeader("Content-Type", "text/xml; charset=utf-8");
return true;
}
3) ClientInterceptor modifying the TransportContext. This results in a http 400 Bad Request.
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
TransportContext context = TransportContextHolder.getTransportContext();
HttpComponentsConnection connection =(HttpComponentsConnection) context.getConnection();
connection.addRequestHeader("Content-Type", "text/xml; charset=utf-8");
return true;
}
Is there a 'proper' Spring Boot way to modify the content-type?
I have a web application built using Spring Boot with Apache Camel and I'm implementing a REST interface.
Currently, using either Camel default Servlet or Restlet component, I'm not getting the HTTP Status code reason in the response.
Here is an example response I'm getting while setting the HTTP Status code to 403:
< HTTP/1.1 403
< Date: Mon, 19 Feb 2018 10:01:21 GMT
< Server: Restlet-Framework/2.4.0
< Content-Type: application/json
< Content-Length: 75
How it should be:
< HTTP/1.1 403 Forbidden
< Date: Mon, 19 Feb 2018 10:01:21 GMT
< Server: Restlet-Framework/2.4.0
< Content-Type: application/json
< Content-Length: 75
How can I configure Camel/Restlet/Servlet to include the reason on the HTTP Status code?
My current configuration:
Application.java
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
private static final Logger appLogger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
appLogger.info("--Application Started--");
}
#Bean
public ServletRegistrationBean servletRegistrationBean() {
SpringServerServlet serverServlet = new SpringServerServlet();
ServletRegistrationBean regBean = new ServletRegistrationBean(serverServlet, "/*");
Map<String,String> params = new HashMap<>();
params.put("org.restlet.component", "restletComponent");
regBean.setInitParameters(params);
return regBean;
}
#Bean
public Component restletComponent() {
return new Component();
}
#Bean
public RestletComponent restletComponentService() {
return new RestletComponent(restletComponent());
}
}
Route Configuration:
#Component
public class RestRouteBuilder extends RouteBuilder {
private static final Logger appLogger = LoggerFactory.getLogger(RestRouteBuilder.class);
private Predicate isAuthorizedRequest = header(HttpHeaders.AUTHORIZATION).isNotNull();
#Override
public void configure() throws Exception {
restConfiguration().component("restlet")
.contextPath("/overlay")
.bindingMode(RestBindingMode.json)
.skipBindingOnErrorCode(false)
.dataFormatProperty("prettyPrint", "true");
rest("/")
.get()
.route()
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
.setBody(constant("Forbidden"))
.endRest();
}
}
I also tried adding .setHeader(Exchange.HTTP_RESPONSE_TEXT, constant("Forbidden")) but the result was the same.
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
.setHeader(Exchange.CONTENT_TYPE, constant("text/plain"))
.setBody(constant("Forbidden"))
How can I configure Camel/Restlet/Servlet to include the reason on the HTTP Status code?
Without a custom core, I believe you can't:
The response is being sent at org.restlet.engine.adapter.ServerCall.sendResponse(), where the response head and body are written to the OutputStream:
writeResponseHead(response); // <--
if (responseEntity != null) {
responseEntityStream = getResponseEntityStream();
writeResponseBody(responseEntity, responseEntityStream);
}
... and writeResponseHead(response) does nothing by default, check it:
protected void writeResponseHead(Response response) throws IOException {
// Do nothing by default
}
Update: ... the HttpStatus(value, reasonPhrase) has the reasonPhrase, but isn't used to stringify:
HttpStatus(int value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
...
#Override
public String toString() {
return Integer.toString(this.value);
}
Update 2: ... the DefaultRestletBinding.populateRestletResponseFromExchange does the following:
// get response code
Integer responseCode = out.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
if (responseCode != null) {
response.setStatus(Status.valueOf(responseCode));
}
... it only uses the Status.valueOf.
Although there is a Status.reasonPhrase, it isn't accessible.
Answer:
Without custom core, (I believe) you can't!
... what isn't inappropriate, given that:
RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1:
6.1.1 Status Code and Reason Phrase
(...) The client is not required to examine or display the Reason-Phrase.
(...) The reason phrases (...) MAY be replaced by local equivalents without affecting the protocol.
RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing:
3.1.2. Status Line
(...) A client SHOULD ignore the reason-phrase content.
RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2):
8.1.2.4. Response Pseudo-Header Fields
(...) HTTP/2 does not define a way to carry the version or reason phrase that is included in an HTTP/1.1 status line.
Need to know the meaning of a status code?
See the complete list of status codes maintained by IANA.
TLDR - use .setHeader(Exchange.HTTP_RESPONSE_CODE, 403)
I found out that
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403)) does not work but
.setHeader(Exchange.HTTP_RESPONSE_CODE, 403) DOES.
Mind the constant keyword difference. You need to use constant when setting values directly in the route, but e.g. in Processor you do not. Therefore you can set response status code whenever exception occurs in code, not in the route builder config.
I have the following REST API to parse the given JSON:
POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.TEXT_PLAIN)
#Path("/test")
public String getText(#FormDataParam("file") InputStream fileInputStream, #FormDataParam("file") FormDataContentDisposition fileDetail) throws Exception {
when I test it using the chrome extension postman, the filedetail.getName() is working however the input stream received is null. here the post request I sent :
POST /parse/test HTTP/1.1
Host: localhost:8080
Cache-Control: no-cache
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="file"; filename="1.json"
Content-Type:
----WebKitFormBoundaryE19zNvXGzXaLvS5C
The inputstream received is null .
Note: if I set the content type to "multipart/form-data" I got an exception :
java.lang.NullPointerException
com.sun.jersey.multipart.impl.MultiPartReaderClientSide.unquoteMediaTypeParameters(MultiPartReaderClientSide.java:245)
com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readMultiPart(MultiPartReaderClientSide.java:172)
com.sun.jersey.multipart.impl.MultiPartReaderServerSide.readMultiPart(MultiPartReaderServerSide.java:80)
com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:158)
com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:85)
com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:490)
com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:555)
com.sun.jersey.multipart.impl.FormDataMultiPartDispatchProvider$FormDataInjectableValuesProvider.getInjectableValues(FormDataMultiPartDispatchProvider.java:122)
com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$EntityParamInInvoker.getParams(AbstractResourceMethodDispatchProvider.java:153)
com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$TypeOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:183)
so I send it without any header, how can I read the file I sent from the postman, is there anything wrong with my REST API ?
do you use org.glassfish.jersey.bundle (jaxrs-ri) ?
if you do, you have to add MultiPartFeature.class to your ApplicationConfigure.java (which contains the Override of getClasses())
if you use grizzly so you have to put and register that class in ResourceConfig.
here an example for both
first grizzly
public static HttpServer startServer() {
final ResourceConfig rc = new ResourceConfig().packages(true, "your.controllers.package");
rc.register(MultiPartFeature.class);
return GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:8080/"),rc);
}
now the jersey
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
addResources(resources);
return resources;
}
private void addResources(Set<Class<?>> resources) {
resources.add(MultiPartFeature.class);
}
I also remove Consumes annotation from my method (I believe it detect it as multipart/form-data by default) and also remove content-type from client request because in this case, it cause error 400
I know the question is weird. Unfortunately I have a service that requires everything to have the header ContentType=application/x-www-form-urlencoded, eventhough the body is JSON
I am trying to use JAX-RS 2.0 ClientBuilder to call it:
String baseUrl = "http://api.example.com/";
JSONObject body = new JSONObject();
body.put("key", "value");
Client client = ClientBuilder.newClient();
client.register(new LoggingFilter());
Builder builder = client.target(baseUrl).path("something").request();
Invocation inv = builder
.header("Content-type", MediaType.APPLICATION_FORM_URLENCODED)
.buildPost(Entity.json(body));
Response response = inv.invoke();
int status = response.getStatus();
// I get 415, unsupported media type (in this case is unexpected)
I have checked my logs and I eventhough I am setting application/x-www-form-urlencoded (via the MediaType) the request appearantly has the Content-type of application/json
How can I force the request to have the Content-type I want?
BTW: This is my custom logger:
public class LoggingFilter implements ClientRequestFilter {
private static final Logger LOG = Logger.getLogger(LoggingFilter.class.getName());
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
LOG.log(Level.INFO, "body");
LOG.log(Level.INFO, requestContext.getEntity().toString());
LOG.log(Level.INFO, "headers");
LOG.log(Level.INFO, requestContext.getHeaders().toString());
}
}
And these are the logs I get:
com.acme.LoggingFilter I body
com.acme.LoggingFilter I {"key":"value"}
com.acme.LoggingFilter I headers
com.acme.LoggingFilter I {Content-type=[application/json]}
The problem with trying to use one of the static Entity helper methods is that it overrides any previous Content-Type header you may have set. In your current case, Entity.json automatically sets the header to application/json.
Instead of using the .json method, you can just use the general purpose Entity.entity(Object, MediaType) method. With your current case though, you can just do Entity.entity(body, MediaType.APPLICATION_FORM_URLENCODED_TYPE) though. The reason is that the client will look for a provider that knows how to serialize a JSONObject to application/x-www-form-urlencoded data, which there is none. So you will need to first serialize it to a String. That way the provider that handles application/x-www-form-urlencoded doesn't need to serialize anything. So just do
Entity.entity(body.toString(), MediaType.APPLICATION_FORM_URLENCODED_TYPE);