Non-standard HTTP methods and Apache Http Components - java

I'm writing an HTTP server using the HttpCore library of Apache HTTPComponents 4.3 (Java). My server must be able to receive requests that have non-standard HTTP methods (methods other than GET, POST, DELETE, etc).
But when my server receives such a request, it returns a "method not supported" response. Is there a way to force HTTPComponents to accept non-standard HTTP methods?
Background: I'm working on implementing a WebDAV server, which uses non-standard methods (like MKCOL and PROPFIND).

I found the solution, so I will answer my own question. xD
You have to create your own HttpRequestFactory implementation, and pass it up the chain.
HttpRequestFactory reqFact = new HttpRequestFactory() {
public HttpRequest newHttpRequest(final RequestLine requestline) throws MethodNotSupportedException {
return new BasicHttpEntityEnclosingRequest(requestline);
}
public HttpRequest newHttpRequest(final String method, final String uri) throws MethodNotSupportedException {
return new BasicHttpEntityEnclosingRequest(method, uri);
}
};
HttpMessageParserFactory<HttpRequest> parserFact = new DefaultHttpRequestParserFactory(null, reqFact);
HttpConnectionFactory<DefaultBHttpServerConnection> connFact = new DefaultBHttpServerConnectionFactory(null, parserFact, null)
The implementation that HttpComponents uses by default throws a MethodNotSupportedException if a non-standard HTTP method is found. The source code for the default implementation can be found here:
http://hc.apache.org/httpcomponents-core-4.3.x/httpcore/xref/org/apache/http/impl/DefaultHttpRequestFactory.html

Related

How to disable cookies with Spring WebClient

Does anyone know if there's a way to disable cookie management in Spring WebClient using Reactor Netty HttpClient?
I noticed both WebClient.Builder and HttpClient APIs provide a means to add cookies to an outbound request but I am looking for a way to inhibit them altogether if such exists. That is akin to disableCookieManagement on Apache's HttpComponentClientBuilder.
It looks like there's no way to disable the cookie handling per se but this seems to work: Create your own HttpClient, then use HttpClient.doOnRequest to register a callback to be called before sending the request. In the callback, call HttpClientRequest.requestHeaders() to retrieve the request headers, then delete the Cookie header.
Sample code that removes User-Agent header before sending the request.
public class Main {
public static void main(String[] args) {
HttpClient httpClient = HttpClient.create().doOnRequest((request, connection) -> {
request.requestHeaders().remove("User-Agent");
});
WebClient client = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Mono<String> r = client.get().uri("https://www.google.com").retrieve()
.bodyToMono(String.class);
System.out.println(r.block());
}
}
In my testing, I'm not seeing that Reactor Netty HttpClient is doing anything at all with the cookies that come back in the response. I was actually looking to enable a cookie store like the Apache HttpClient supports, but to all indications the Netty HttpClient has no such feature.
In the entire codebase of Reactor Netty, there is no usage of the "set-cookie" header except in one of their unit tests. This tells me cookies are ignored by default unless the client code chooses to look for them.

Logging Rest Assured's own headers

I'm trying to get access to the HTTP headers that are injected by Rest Assured. Spring's Mock MVC gives you access to pretty much everything via the MvcResult, and you can use this result to log pretty much anything you would like about the request and response. The only way I can see how to do this is in RestAssured is with a Filter. However, it gives you limited access to the request (you just get the RequestSpecification). I understand that it might be tricky to get access to headers that are added by the HttpClient, but it doesn't look like you can even get access to headers that are added by Rest Assured itself. For example, I can't see any OAuth related headers, nor content-type or content-length. The only headers that appear are those that were manually added using, for example, .contentType(ContentType.XML)
Is there any other way to get access to those headers? I don't need to modify the request, I just want to be able to log all of it and the headers that are injected by Rest Assured.
I found that it's possible to register your own HttpClientFactory with RestAssured:
RestAssured.config().httpClient(
HttpClientConfig.httpClientConfig().httpClientFactory(
new CustomHttpClientFactory())
So I created a new factory that returns an HTTP client into which I inject some request and response interceptors.
public class CustomHttpClientFactory extends HttpClientConfig.HttpClientFactory {
#Override
public HttpClient createHttpClient() {
DefaultHttpClient client = new DefaultHttpClient();
client.addRequestInterceptor((request, ctx) -> {
// do what you will
});
client.addResponseInterceptor((response, ctx) -> {
// do what you will
});
return client;
}
}
This gives you almost full access to manipulate the request and response. One thing to remember is that if you're going to read from the response's entity, you should first wrap it in a BufferedHttpEntity to make it re-readable:
if (response.getEntity() != null && !response.getEntity().isRepeatable()) {
response.setEntity(new BufferedHttpEntity(response.getEntity()));
}
Another problem I ran into is when trying to see the OAuth related information. When using RestAssured's OAuth functionality, it adds its own OAuthSigner interceptor to the HTTP client right before executing the request. This means that it will always be the last interceptor to be called and any interceptor you may have already injected will be called before the request ever gets signed. Because I don't really need to see the signature for now, I didn't investigate this further and I'm leaving it as an exercise for the reader. ;)

Do SOAP Web services support only "POST" http method

I faced this question on one of interviews, so could you please tell whether SOAP Web services support only "POST" http method or there is some way to accept other methods on the server side?
I always used POST but according to the W3C standard, SOAP supports both POST and GET methods.
Edit: After some research, it seems that it's not completely true, as you can see here. It is theoretically possible to use GET because POST and GET are methods of HTTP transport protocol and SOAP can be used over HTTP.
But as you know, GET includes the request in the query string. SOAP requests (XML messages) are usually too complex and verbose to be included in the query string, so almost every implementation (for example JAX-WS) supports only POST.
Thread is three years old but I think that there will be still a lot of people who will give this same question to themselves and will find wrong answer in the web. The answer to the question is no, GET method can be used too.
According to SOAP specification, which can found here: https://www.w3.org/TR/2007/REC-soap12-part0-20070427/#transport
both GET and POST methods can be used to exchange SOAP messages over http.
The use of the HTTP POST method for conveying SOAP messages in the bodies of HTTP request uses a pattern called SOAP request-response message exchange pattern. In the case of HTTP GET a pattern is used called SOAP response message exchange pattern. The main difference of this two patterns is:
The first type of interaction allows for the use of data within the body of a HTTP POST to create or modify the state of a resource identified by the URI to which the HTTP request is destined. The second type of interaction pattern offers the ability to use a HTTP GET request to obtain a representation of a resource without altering its state in any way. In the first case, the SOAP-specific aspect of concern is that the body of the HTTP POST request is a SOAP message which has to be processed (per the SOAP processing model) as a part of the application-specific processing required to conform to the POST semantics. In the second case, the typical usage that is forseen is the case where the representation of the resource that is being requested is returned not as a HTML, or indeed a generic XML document, but as a SOAP message. That is, the HTTP content type header of the response message identifies it as being of media type "application/soap+xml"
So both GET and POST methods can be used. The other thing is that in practice mostly POST method is used.
The bad thing is that when comparing RESTful services with SOAP services, as an advantage of REST people are bringing caching, which is not available in SOAP, because SOAP uses only POST. This is totally wrong.
This is an implementation of GET in SOAP:
#WebServiceProvider(targetNamespace="http://attachment.service.soap.com/download")
#ServiceMode(value = javax.xml.ws.Service.Mode.MESSAGE)
#BindingType(value = HTTPBinding.HTTP_BINDING)
public final class ImageDownloadServiceProvider implements Provider<DataSource> {
#Resource
private WebServiceContext wsContext;
#Override
public DataSource invoke(DataSource request) {
if (wsContext == null)
throw new RuntimeException("dependency injection failed on wsContext");
MessageContext msgContext = wsContext.getMessageContext();
HttpExchange exchange = (HttpExchange) msgContext.get("com.sun.xml.internal.ws.http.exchange");
String filename = exchange.getRequestURI().getQuery().replace("file=", "");
switch ((String) msgContext.get(MessageContext.HTTP_REQUEST_METHOD)) {
case "GET":
return doGet(filename);
default:
throw new HTTPException(405);
}
}
private DataSource doGet(String filename) {
FileDataSource fds = new FileDataSource(filename);
MimetypesFileTypeMap mtftm = new MimetypesFileTypeMap();
mtftm.addMimeTypes("image/jpeg jpg");
fds.setFileTypeMap(mtftm);
return fds;
}

Unable to override a response header using a Jersey client filter

I am trying to use the Jersey client API to consume a third-party REST service. I plan to use the automatic POJO deserialisation to go from JSON responses to Java objects.
Unfortunately, the third party service returns the responses using the content type "text/javascript". My Jersey client fails to understand that this should be considered as a JSON object and fails to deserialise the object.
I wrote a simple Jersey server application to verify that by changing the content type from "text/javascript" to "application/json" that the deserialisation works.
Armed with this information, I set about to use a Jersey client filter to modify the response headers. The code comes from a comment by the author of this question. In fact, the question appears to be exactly the same as mine - however the answerer mis-answered the question and shows how to modify the request headers (rather than the response headers). The original author was able to use the answer to create his solution, but, it seems his stated solution fails to work.
The filter code is:
client.addFilter(new ClientFilter() {
#Override public ClientResponse handle(ClientRequest cr)
throws ClientHandlerException {
ClientResponse response = getNext().handle(cr);
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
return response;
}
});
When executed however, an UnsupportedOperationException is raised:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableCollection.clear(Collections.java:1035)
at com.sun.jersey.core.util.StringKeyIgnoreCaseMultivaluedMap.putSingle(StringKeyIgnoreCaseMultivaluedMap.java:78)
at com.sun.jersey.core.util.StringKeyIgnoreCaseMultivaluedMap.putSingle(StringKeyIgnoreCaseMultivaluedMap.java:56)
at App$1.handle(App.java:49)
at com.sun.jersey.api.client.Client.handle(Client.java:648)
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:680)
at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:507)
at App.main(App.java:63)
The returned headers appear to be wrapped in an unmodifiable collection.
I then attempted to copy all of the headers to a new collection, but there is no way that I can see to set a map of headers back into the response.
Finally, I thought perhaps I can create a new ClientResponse containing my amended headers. However, the constructor for ClientResponse has this signature:
public ClientResponse(int status,
InBoundHeaders headers,
InputStream entity,
MessageBodyWorkers workers)
It is trivial to copy the status, headers and entity variables from the original. However, I can see no way of getting a reference to the workers field.
How can I use a Jersey client filter to modify the response header from "text/javascript" to "application/json" so that my POJO deserialisation will work?
In Jersey 2, register an implementation of a ClientResponseFilter with the ClientConfig in order to manipulate the HTTP headers of incoming responses.
For example, this seems to work well with Jersey 2.3.1 for manipulating HTTP header:
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.client.ClientRequestContext;
import org.glassfish.jersey.client.ClientConfig;
/* Ensure that there is an "application/xml" Content-Type header on
* successful responses without a content type header. */
#Provider
public static class EnsureXmlContentTypeFilter implements ClientResponseFilter {
#Override
public void filter(ClientRequestContext requestContext,
ClientResponseContext responseContext) {
if (200 == responseContext.getStatus() &&
null == responseContext.getHeaderString(HttpHeaders.CONTENT_TYPE)) {
responseContext.getHeaders().add(
HttpHeaders.CONTENT_TYPE, "application/xml"
);
}
}
}
private final ClientConfig config = new ClientConfig()
// Registering this filter adds a "Content-Type: application/xml"
// header to each response that lacks Content-Type headers.
.register(EnsureXmlContentTypeFilter.class)
;
private final Client client = ClientBuilder.newClient(config);
The Jersey documentation on Filters and Interceptors isn't perfect, but it does have some links to the javadocs for the relevant classes: https://jersey.java.net/documentation/latest/filters-and-interceptors.html
I am getting XML responses from a service which responds with XML content, but lacks a "Content-Type: application/xml" header. Probably a better approach would be to register MessageBodyReaders, but the above approach works while I'm playing around with that service's API.
I don't have an answer to your real question, but I think I see how you can get that workers instance if you want to try to create a new response in your filter.
The "workers" object that you need appears to be a singleton. If you can get hold of your com.sun.jersey.api.client.Client instance, you can retrieve the workers object. In my case, the Jersey client code is in a unit test which subclassed JerseyTest. JerseyTest defines a method "client()" which returns the Client object. I added the following test code (well not exactly but close):
MessageBodyWorkers workers = client().getMessageBodyWorkers();
Then I set a breakpoint in the constructor of ClientResponse (this is the original ClientResponse returned by Jersey. I have not attempted to clone it because I don't need to for my test). The workers passed to the constructor was the same instance. So, even though you can not get the workers object from the response object, you should be able to get it elsewhere.
Guido's answer provides the insight required to create a new ClientResponse object and return it instead. For reasons that I've not yet bothered to track down, creating a new InboundHeaders, adding all the existing headers to it, and then modifying the single header in question still fails with an UnsupportedOperationException. As such, to re-write the headers, we iterate over the original headers and build the correct set iteratively:
final Client client = Client.create(clientConfig);
client.addFilter(new ClientFilter()
{
#Override
public ClientResponse handle(ClientRequest cr) throws ClientHandlerException
{
final ClientResponse response = getNext().handle(cr);
final InBoundHeaders headers = new InBoundHeaders();
for (String header : response.getHeaders().keySet())
{
if (header.equals(HttpHeaders.CONTENT_TYPE))
{
headers.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
}
else
{
headers.put(header, headers.get(header));
}
}
return new ClientResponse(response.getStatus(),
headers,
response.getEntityInputStream(),
client.getMessageBodyWorkers());
}
}
In Jersey 2, you should use a ClientResponseFilter. Then you can just call responseContext.getHeaders().putSingle(...).
Under Java 8 you can do it with a lambda:
client.register((ClientResponseFilter) (requestContext, responseContext) ->
responseContext.getHeaders().putSingle("Content-Type", "application/json"));
If you want to re-use an existing filter instance, just register it on the Client instead of on the ClientConfig.
Old way (Jersey-1.9):
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
Client client = new Client();
client.addFilter(new HTTPBasicAuthFilter(username, password));
New way (Jersey-2.3):
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import org.glassfish.jersey.client.filter.HttpBasicAuthFilter;
Client client = ClientBuilder.newClient();
client.register(new HttpBasicAuthFilter(username, password));
That's not the best solution, but it may help you to migrate.

get content of http request in REST web service using java

Anyone know how to get content of httprequest in REST webservice using java?
thanks
You can inject context about the individual requests. As an example, the code snippet below shows how the HTTP request headers can be injected.
#GET
#Produces{"text/plain"}
public String listHeaderNames(#Context HttpHeaders headers) {
StringBuilder buf = new StringBuilder();
for (String header: headers.getRequestHeaders().keySet()) {
buf.append(header);
buf.append("\n");
}
return buf.toString();
}
See the relevant part of the JAX-RS 1.1 specification for more information.
Look at Restlet
// Create the client resource
ClientResource resource = new ClientResource("http://www.restlet.org");
// Write the response entity on the console
resource.get().write(System.out);

Categories