I'm using Jersey 1.17.1 and on every URL I've created I want to allow people to put ".json" at the end or not. Here's an example of what I've done:
#GET
#Path("basepath{extension: (\\.json)?}")
public String foobar() {
...
}
Eventually I'm going to let them choose between nothing, ".json" or ".xml" and I'm concerned about my DRY violation here. I'll have to change every #Path to this instead:
#GET
#Path("basepath{extension: (\\.json|\\.xml)?}")
public String foobar() {
...
}
Is there a better way to do this that lets my path value be more reusable? Although I can't use Jersey 2.0, I'd be interested to know if it can solve this problem.
One way to do this is to subclass PackagesResourceConfig and inform Jersey which extensions should map to which media types. For instance:
public class ExampleResourceConfig extends PackagesResourceConfig {
#Override
public Map<String, MediaType> getMediaTypeMappings() {
Map<String, MediaType> map = new HashMap<>();
map.put("xml", MediaType.APPLICATION_XML_TYPE);
map.put("json", MediaType.APPLICATION_JSON_TYPE);
return map;
}
}
and then your actual REST service might look like:
#GET
#Path("basepath")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response foobar() {
...
}
Jersey will select the appropriate media type based on the url extension. Note that Response is returned instead of String. I'm not sure how you're building your response and what your requirements are but Jersey can handle converting your Java beans into either XML or JSON (or even JSONP) without a problem.
In the REST API implementation , the resource representation can be either xml or json or etc. This is not a good way of restful implementation if you specify the types as the extensions of the URL. The correct way is to use HTTP ACCEPT header
like Accept: application/json or
Accept: application/xml
Refer : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
Related
I have looked at the various answers and they do not resolve my issue. I have a very specific client need where I cannot use the body of the request.
I have checked these posts:
Trying to use Spring Boot REST to Read JSON String from POST
Parsing JSON in Spring MVC using Jackson JSON
Pass JSON Object in Rest web method
Note: I do encode the URI.
I get various errors but illegal HTML character is one. The requirement is quite simple:
Write a REST service which accepts the following request
GET /blah/bar?object=object11&object=object2&...
object is a POJO that will come in the following JSON format
{
"foo": bar,
"alpha": {
"century": a,
}
}
Obviously I will be reading in a list of object...
My code which is extremely simplified... as below.
#RequestMapping(method=RequestMethod.GET, path = "/test")
public Greeting test(#RequestParam(value = "object", defaultValue = "World") FakePOJO aFilter) {
return new Greeting(counter.incrementAndGet(), aFilter.toString());
}
I have also tried to encapsulate it as a String and convert later which doesnt work either.
Any suggestions? This should really be extremely simple and the hello world spring rest tut should be a good dummy test framework.
---- EDIT ----
I have figured out that there is an underlying with how jackson is parsing the json. I have resolved it but will be a write up.. I will provide the exact details after Monday. Short version. To make it work for both single filter and multiple filters capture it as a string and use a json slurper
If you use #RequestParam annotation to a Map<String, String> or MultiValueMap<String, String> argument, the map will be populated with all request parameters you specified in the URL.
#GetMapping("/blah/bar")
public Greeting test(#RequestParam Map<String, String> searchParameters) {
...
}
check the documentation for a more in depth explanation.
I am currently implementing a web API
Spring
Jersey
com.thetransactioncompany.cors http://software.dzhuvinov.com/cors-filter.html
The output (if any) will be JSON, so all my classes are annotated with the expected media type.
#Produces(MediaType.APPLICATION_JSON)
public class CustomerResource {
...
}
that way my classes are automatically transformed to json.
BUT...
Due to microsoft, their IE only support CORS, if the request/response type is text/plain http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
4. Only text/plain is supported for the request's Content-Type header
so I need to force my application to respond with text/plain in the header but still projecting my classes to json output. I know that the CORS classes I added is setting that header, but somehow that is overwritten again by my annotation, even if I add another filter by my own.
Hum, the link you are pointing says that it is true for REQUESTS only.
So you can accept only text plain but are free to produce what ever you want.
EDIT Try registering a custom responsefilter with code similar to that (maybe you already did it?):
#Provider
public class HeaderRewriteFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
response.setResponse(Response
.fromResponse(response.getResponse()).header(HttpHeaders.CONTENT_TYPE, "text/plain").build());
return response;
}
}
However check the result to ensure it is ok if the response already contains this header.
Otherwise you can try to modify the current response, but I am not sure you can as it might be an immutable object. And by the way it looks less clean to me :)
List<Object> contentTypes = response.getHttpHeaders().get(HttpHeaders.CONTENT_TYPE);
contentTypes.clear();
contentTypes.add("text/plain");
Also for doing json<>java databiding you can check Genson library http://code.google.com/p/genson/, it integrates well with Jersey. Just drop the jar in the classpath and run!
EDIT 2 OK then you must do it in the other way, use produces "text/plain" and define a json bodywriter for for that type. Downside is that you will be able to produce only json. With Genson you could do it that way:
#Provider
#Produces({ MediaType.TEXT_PLAIN })
public class PlainTextJsonConverter extends GensonJsonConverter {
public GensonJsonConverter() {
super();
}
public GensonJsonConverter(#javax.ws.rs.core.Context Providers providers) {
super(providers);
}
}
I am using CXF with JacksonJsonProvider for my REST Services. I have a test method as follows
#POST
#Path("/book/{id}")
#Consumes({"application/json, multipart/form-data, application/x-www-urlencoded"})
#Produces({"application/json"})
public boolean setOwner(Book book) {
System.out.println(book.getName());
return true;
}
Now if I make a POST request with a raw JSON string as follows
{"Book":{"name":"Book name","publisher":"Book publisher"}}
The request is processed correctly as I use Content-Type as 'application/json' while making the request.
But since I am integrating with an external service, I recieve either multipart/form-data OR application/x-www-urlencoded for which there is nothing afaik in Jackson that can handle it. If someone can point me to the right direction that would be great.
I can manage multipart/form-data with Jettison (part of CXF) but I would like to use Jackson.
I was looking to do exactly the same thing, almost to years later! I didn't have much luck using one method to handle multiple mime times, but I did get it to work using two methods, for example:
#POST
#Path("/book/{id}")
#Consumes({"multipart/form-data"})
#Produces({"application/json"})
public boolean setOwnerFromUpload(#FormDataParam("file") InputStream inputStream) {
// decode
final ObjectMapper mapper = new ObjectMapper();
final Book book = (Book) mapper.readValue(inputStream, Book.class);
System.out.println(book.getName());
return true;
}
With two methods, jackson can now handle the the two different mime types.
My POST method looks like this:
#POST
#Consumes({"application/json"})
#Path("create/")
public void create(String param1, String param2){
System.out.println("param1 = " + param1);
System.out.println("param2 = " + param2);
}
When I create a Jersey Client in Netbeans the method who calls the post method looks like this:
public void create(Object requestEntity){
webResource.path("create").type(MediaType.APPLICATION_JSON).post(requestEntity);
}
When running this test:
#Test
public void hello(){
String json = "{param1=\"hello\",param2=\"hello2\"}";
this.client.create(json);
}
It gives the following output in the server:
INFO: param1 = {param1="hello",param2="hello2"}
INFO: param2 =
What do I need to change so that the parameters are giving the correct value?
Your #POST method should be accepting a JSON object instead of a string. Jersey uses JAXB to support marshaling and unmarshaling JSON objects (see the jersey docs for details). Create a class like:
#XmlRootElement
public class MyJaxBean {
#XmlElement public String param1;
#XmlElement public String param2;
}
Then your #POST method would look like the following:
#POST #Consumes("application/json")
#Path("/create")
public void create(final MyJaxBean input) {
System.out.println("param1 = " + input.param1);
System.out.println("param2 = " + input.param2);
}
This method expects to receive JSON object as the body of the HTTP POST. JAX-RS passes the content body of the HTTP message as an unannotated parameter -- input in this case. The actual message would look something like:
POST /create HTTP/1.1
Content-Type: application/json
Content-Length: 35
Host: www.example.com
{"param1":"hello","param2":"world"}
Using JSON in this way is quite common for obvious reasons. However, if you are generating or consuming it in something other than JavaScript, then you do have to be careful to properly escape the data. In JAX-RS, you would use a MessageBodyReader and MessageBodyWriter to implement this. I believe that Jersey already has implementations for the required types (e.g., Java primitives and JAXB wrapped classes) as well as for JSON. JAX-RS supports a number of other methods for passing data. These don't require the creation of a new class since the data is passed using simple argument passing.
HTML <FORM>
The parameters would be annotated using #FormParam:
#POST
#Path("/create")
public void create(#FormParam("param1") String param1,
#FormParam("param2") String param2) {
...
}
The browser will encode the form using "application/x-www-form-urlencoded". The JAX-RS runtime will take care of decoding the body and passing it to the method. Here's what you should see on the wire:
POST /create HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 25
param1=hello¶m2=world
The content is URL encoded in this case.
If you do not know the names of the FormParam's you can do the following:
#POST #Consumes("application/x-www-form-urlencoded")
#Path("/create")
public void create(final MultivaluedMap<String, String> formParams) {
...
}
HTTP Headers
You can using the #HeaderParam annotation if you want to pass parameters via HTTP headers:
#POST
#Path("/create")
public void create(#HeaderParam("param1") String param1,
#HeaderParam("param2") String param2) {
...
}
Here's what the HTTP message would look like. Note that this POST does not have a body.
POST /create HTTP/1.1
Content-Length: 0
Host: www.example.com
param1: hello
param2: world
I wouldn't use this method for generalized parameter passing. It is really handy if you need to access the value of a particular HTTP header though.
HTTP Query Parameters
This method is primarily used with HTTP GETs but it is equally applicable to POSTs. It uses the #QueryParam annotation.
#POST
#Path("/create")
public void create(#QueryParam("param1") String param1,
#QueryParam("param2") String param2) {
...
}
Like the previous technique, passing parameters via the query string does not require a message body. Here's the HTTP message:
POST /create?param1=hello¶m2=world HTTP/1.1
Content-Length: 0
Host: www.example.com
You do have to be particularly careful to properly encode query parameters on the client side. Using query parameters can be problematic due to URL length restrictions enforced by some proxies as well as problems associated with encoding them.
HTTP Path Parameters
Path parameters are similar to query parameters except that they are embedded in the HTTP resource path. This method seems to be in favor today. There are impacts with respect to HTTP caching since the path is what really defines the HTTP resource. The code looks a little different than the others since the #Path annotation is modified and it uses #PathParam:
#POST
#Path("/create/{param1}/{param2}")
public void create(#PathParam("param1") String param1,
#PathParam("param2") String param2) {
...
}
The message is similar to the query parameter version except that the names of the parameters are not included anywhere in the message.
POST /create/hello/world HTTP/1.1
Content-Length: 0
Host: www.example.com
This method shares the same encoding woes that the query parameter version. Path segments are encoded differently so you do have to be careful there as well.
As you can see, there are pros and cons to each method. The choice is usually decided by your clients. If you are serving FORM-based HTML pages, then use #FormParam. If your clients are JavaScript+HTML5-based, then you will probably want to use JAXB-based serialization and JSON objects. The MessageBodyReader/Writer implementations should take care of the necessary escaping for you so that is one fewer thing that can go wrong. If your client is Java based but does not have a good XML processor (e.g., Android), then I would probably use FORM encoding since a content body is easier to generate and encode properly than URLs are. Hopefully this mini-wiki entry sheds some light on the various methods that JAX-RS supports.
Note: in the interest of full disclosure, I haven't actually used this feature of Jersey yet. We were tinkering with it since we have a number of JAXB+JAX-RS applications deployed and are moving into the mobile client space. JSON is a much better fit that XML on HTML5 or jQuery-based solutions.
So I wrote a sample REST resource that works like a charm in Jersey/Tomcat, but when I take it to RestEASY/Tomcat it blows. I mean really? what happened to working out of the box. Anyway a little frustrated. I get this error when trying to access the resource(http://localhost:7070/mg/mytest)
"content-type was null and expecting to extract a body"
7842 [http-7070-2] ERROR com.loyalty.mg.rest.exception.MGExceptionMapper - Error caught in the exception mapper -
org.jboss.resteasy.spi.BadRequestException: content-type was null and expecting to extract a body
at org.jboss.resteasy.core.MessageBodyParameterInjector.inject(MessageBodyParameterInjector.java:131)
at org.jboss.resteasy.core.MethodInjectorImpl.injectArguments(MethodInjectorImpl.java:98)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:121)
at org.jboss.resteasy.core.ResourceMethod.invokeOnTarget(ResourceMethod.java:247)
at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:212)
at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:202)
#Path("/mytest")
public class TestResource {
#GET
public Response getData()
I guess the question also is - is RestEASY any better than Jersey, this is just the start and I am getting errors. Should I just stick to Jersey?
Also already tried this as well :)
<context-param>
<param-name>resteasy.media.type.mappings</param-name>
<param-value>json : application/json, xml : application/xml</param-value>
</context-param>
The code that throws that exception looks like this:
final MediaType mediaType = request.getHttpHeaders().getMediaType();
if (mediaType == null) {
throw new BadRequestException(
"content-type was null and expecting to extract a body");
}
The problem seems to be that RestEASY cannot figure out a content type from the headers of the request that it received. This suggests that either that the content type in the request is bogus, or that there is a problem with the way that you have configured RestEASY.
I guess the question also is - is RestEASY any better than Jersey, this is just the start and I am getting errors. Should I just stick to Jersey?
I cannot answer that. However, I think you are being too quick to blame RestEASY for something that could be your code's fault.
A classic cause of this, is if you have code like this:
#GET
#Path("/foo/{bar}")
#Produces(MediaType.TEXT_HTML)
public Response foo(#PathParam("bar") String bar) {
...and you forget to annotate the bar argument with #PathParam. Then RestEasy thinks it should be reading bar from the body of the request, instead of from the URL path, and will chuck this exception.
That doesn't seem to be what's happening in your case, but I got the same exception, and this was the cause.
RestEASY vs Jersey is hard to say:
http://www.infoq.com/news/2008/10/jaxrs-comparison
Regarding your error, you can control the content type via annotations, what happens if you place #Produces annotation, for example:
#Produces("application/json")
#GET
public Response getData() {
...
}
Well I know this requested is dated, and so much on the internet old..in a year of two everything usually changes and works better. So RestEasy should not get a bad rap in comparison to other non-propertary RESTLET frameworks.
Actually I think JBoss RestEasy has the lightest footprint, it's not bloated with unnecessary *.jars, flexible, fully certified JAX-RS implementation, complete and its ease of use is beyond comparison.
Some eluded, that a GET request should not expect a Content_Type on the request, (And I agree), but with a every GET request one must indicate what you intend on sending back to the requestor? Right! (will it be JSON, XML, plain text, XML and a sheetsheet, multi-part, etc). Well RestEasy, JBoss's framework addresses this with annotation as shown below, and configurable per URL REST request. Therefore, therein is your answer
#GET
#Path("/echo/{message}")
#Produces("text/plain")
public String echo(#PathParam("message")String message){
return message;
}
#GET
#Path("/employees")
#Produces("application/xml")
#Stylesheet(type="text/css", href="${basepath}foo.xsl")
public List<Employee> listEmployees(){
return new ArrayList<Employee>(employees.values());
}
#GET
#Path("/employee/{employeeid}")
#Produces("application/xml")
public Employee getEmployee(#PathParam("employeeid")String employeeId){
return employees.get(employeeId);
}
#GET
#Path("/json/employees/")
**#Produces("application/json")**
public List<Employee> listEmployeesJSON(){
return new ArrayList<Employee>(employees.values());
}
a GET request must not have a body, and an application must not expet a Content-Type header.
If this is a bug of RestEASY, it makes one wonder how many people really are using the software.
EDIT
RFC2616 $4.3
A message-body MUST NOT be included in
a request if the specification of the
request method (section 5.1.1) does
not allow sending an entity-body in
requests.
A server SHOULD read and forward a
message-body on any request; if the
request method does not include
defined semantics for an entity-body,
then the message-body SHOULD be
ignored when handling the request.
The GET method does not "does not allow sending an entity-body in request" therefore a GET request COULD have a body. But GET "does not include defined semantics for an entity-body" therefore the body should be ignored anyway.
In any case, RestEASY should not have required the presence of Content-Type in a GET request.