Trying to build a REST-Service with encapsulated http response codes - java

I'm currently trying to build a REST webservice using Java/EE(5) that fully encapsulates the http responses, so every request should give back response code 200 (OK) and should look like this:
{
"msg" : { // imagine some datastructure here },
"error" : {
"code" : 200 // http response code
"status" : "OK" // some string defining this
}
}
My prefered framework is JAX-RS (we plan to migrate to EE6 soon, so migration is one of the topics while developing this), but can JAX-RS do this?

The easiest way to always return 200 OK and Content-Type: application/json with JAX-RS:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
#Path("/not-rest")
#Produces("application/json")
public class NotRestBean {
#GET
public Response getSoapStyle() {
String json = "{}"; // build your response here
return Response.ok(json).build();
}
}
Again, I don't recommend to do this. A central part of REST is the Uniform Interface which includes proper response codes.

Related

How to set Content-Type Header in Java MicroProfile RestClient during method call

I am currently using the Java MicroProfile RestClient and have following problem:
Backend provides an api to receive binary files
Backend would be happy to receive a Content-Type header containing the Mime-Type of the binary file
I am not able to set Content-Type per method parameter
I have following code on client-side:
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.smallrye.mutiny.Uni;
#RegisterRestClient
public interface RestClient {
#POST
#Path("/api/path/v1")
Uni<String> createResource(#HeaderParam("Content-Type") String contentType, byte[] body);
}
The #HeaderParam("Content-Type") will always be overwritten with "application/json".
If I set the #Consumes Property the Content-Type would always be the same but I want to set it during the method call. (byte[] could contain image, video, text ...)
Has anyone an idea how I could archive this?
May there is a better option instead of using a simple byte[] as body?
Best thanks!
Maybe you could try the solution mentioned here: https://quarkus.io/guides/rest-client#custom-headers-support

How to add a body to a HTTP request using Java Spring's RestTemplate's exchange method?

I am trying to send a HTTP request using RestTemplate's exchange method. However, for some reason the HTTP body of the sent request seems to be empty.
Here is the code I currently have (the original code is more complex):
package somepackage;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
public class SomeMainClass {
public static void main(String[] args) {
HttpEntity<String> entity = new HttpEntity<>("body contents", new HttpHeaders());
new RestTemplate().exchange("http://localhost:5678/someRequest", HttpMethod.GET, entity, String.class);
}
}
In order to confirm whether the code above worked, I ran nc -l 5678 (which listens for a request on port 5678) in a terminal, and in my IDE, I ran the above code. The nc command in my terminal printed out a HTTP request that does not have a body (I expected it to have a body with the string "body contents").
Why doesn't this work? How can I fix it?
Note: Here are the requirements that made me decide to use the exchange method
It has to be a GET request.
The request needs to have a body.
I have to set some headers.
I need to read the body of the response.
GET methods don't have body. You might want to change HttpMethod.GET to HttpMethod.POST.
If you want to supply parameters in GET, you can change the URL to something like http://localhost:5678/someRequest?expiry=23000.
More details at Spring RestTemplate GET with parameters.

Delete rest with parameters

I need to write this REST request in java using Httpdelete or any other library.
curl -X DELETE -d '{"ruleid":"1" }' http://192.168.1.1:8080/wm/acl/rules/json
I couldn't find a way to parse the Json data !
Thanks for your help !
Like others said, it is unusual that a DELETE request contains a body. But it is not strictly impossible as long as the server supports it.
There are many ways to build a REST Client in Java (see https://stackoverflow.com/a/5024571/1018443). A common way is to use Jersey 2.
In Jersey 2, the .delete() method does not contain a parameter for the body entity. But you can use .build to create a DELETE request with a body. Here is an example:
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
public class RestClient {
public static void main(String[] args) {
Model model = new Model();
ClientConfig config = new ClientConfig();
config.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target("http://192.168.1.1:8080/");
String response = target
.path("wm/acl/rules/json")
.request(MediaType.APPLICATION_JSON)
.build("DELETE", Entity.entity(model, MediaType.APPLICATION_JSON))
.invoke(String.class);
System.out.println(response);
}
private static class Model {
public int ruleid = 1;
}
}
Note that you need to configure the client with Property ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION = true. Otherwise you get an Exception: Entity must be null for http method DELETE.
You will find many examples on how to build a Java REST client with Jersey. For example: https://howtodoinjava.com/jersey/jersey-restful-client-examples/
You have to use POST request instead of DELETE, because body of DELETE request is ignored.
From spec:
The DELETE method requests that the origin server delete the resource identified by the Request-URI
reference link

POST request not working with JSON input using Restlet

I'm using Restlet framework to implement a POST request for a REST resource, which is supposed to accept JSON formatted data. Problem is, I keep getting the 415 Unsupported Media Type error.
The odd thing is that I've set a breakpoint right inside the function responsible for handling the POST request, and when the input is of application/json, debug does NOT stop at the breakpoint (meaning the function handling POST requests is not even called, and the error just comes beforehand). However if I change the input to multipart/form-data or application/x-www-form-urlencoded, debug DOES stop at the breakpoint. So why isn't the POST function called when input is of application/json type ?
Here is the request:
POST /res2 HTTP/1.1
Host: localhost:8888
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 121a2782-0b4e-f592-8d78-26f07862d5fd
{"id":3,"name":"John Smith","age":23,"gender":"Male"}
The output HTML message states:
The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method.
Main Application code:
package com.poc.hw11;
import xyz (trimming to save space)
public class JSON_POC extends Application
{
#Override
public Restlet createInboundRoot()
{
Router router = new Router(getContext());
router.attach("/res1", Resource1.class);
return router;
}
}
Resource1 Class:
package com.poc.hw11;
import xyz (trimming to save space);
public class Resource1 extends ServerResource
{
#Post
public void addPerson() {
Request request = getRequest(); // BREAKPOINT SET ON THIS LINE. DEBUG DOESN'T REACH THIS POINT WHEN INPUT IS OF application/json TYPE
Response response = getResponse();
//Rest of code here.
}
}
I have also tried changing #Post to #Post("json"), but the result is the same .. Any ideas ?
if you want manually handle the incoming representation, I would you the following syntax:
#Post
public void addPerson(Representation rep) {
System.out.println(rep.getMediaType());
Request request = getRequest();
Response response = getResponse();
}
But I would let automatic converter handle this:
Create a bean Contact having the structure of your json, then let the jackson converter deal with deserialization :
#Post
public void addPerson(Contact contact) {
System.out.println(contact.getName());
}
In order to add the Jackson converter, just complete the classpath of your application with the Jackson extension for Restlet Framework (org.restlet.ext.jackson.jar) and its dependencies (the jackson libraries com.fasterxml.*.jar shipped with Restlet)
Please fell free to ask for more details.
The default content type for post requests is url-encoded form. If you need to handle some other type, you need to find the way to specify that in your code. For example, if I were using Jersey, I would use the #Consumes annotation to specify the expected content type for this post request. You need to find out how to do the same in restlet framework.

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.

Categories