Jersey 1x does not send Content-Length header in request - java

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

Related

How to use SOAP 1.2 with a different Content-Type in Spring Boot

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?

rest assured java.net.SocketException

I am executing a simple post test in rest-assured,
While exeuting post() method I am getting "java.net.SocketException: Operation timed out"
#Test
public void validateGetProductInfo_Prod() throws IOException {
String jsonBody = new String(Files.readAllBytes(Paths.get("src/test/resources/product.json")));
Headers basicHeaders = new Headers(asList(
new Header("X-ApplicationAuthorizationToken", "AAABB"),
new Header("Authorization", "Basic ABC"),
new Header("Content-Type", "application/json")));
String produPath = "http://api.abcd.com/product/v1/product/info";
given().headers(basicHeaders)
.body(jsonBody)
.when()
.post(produPath)
.then().using().defaultParser(Parser.JSON)
.statusCode(200);
}
I had validated that there is no error till the when is executed,
On executign the post() method facing the exception?
The original issue is the service end-point is expecting a user-agent header, the header was not sent in the request so the end-point was not responding.
Once the header was added the issues resolved.
Note:
Postman adds the default chrom user-agent if no header was provided

Content type : "Application/Json" issue with retrofit 2.2 while calling cake php apis

My Api is accepting Content-Type application/json as headers. I set Header perfectly as mentioned in Retrofit Docs.
#Headers("Content-Type: application/json")
#POST("user/classes")
Call<playlist> addToPlaylist(#Body PlaylistParm parm);
I also tried by setting content type in authentication interceptor class:
public class AuthenticationInterceptor implements Interceptor {
private String authToken;
public AuthenticationInterceptor(String token) {
this.authToken = token;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder()
.addHeader("Content-type","application/json")
.addHeader("Authorization", authToken);
Request request = builder.build();
return chain.proceed(request);
}
}
But in Request Log it is Returning Content-Type txt/html.So how i should fix this issue? This api works fine in POSTMAN
I tried with all possible ways but it's not working with cake php web services.
Any help would be appreciated.

Send JSON body but with ContentType=application/x-www-form-urlencoded with ClientBuilder

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

Jersey 2.0 Content-Length not set

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

Categories