We want to deliver some HTML files as well as other files through a Spring Boot Webapp. We want to split the HTML and non HTML traffic by craeting a REST endpoint just for URIs that end with .html.
We tried something like this without success :(
#GetMapping(value = "/{*html}")
public String deliverHtml(HttpServletResponse response, HttpServletRequest request) {
return doSomeMagicStuff();
}
#GetMapping(value = "{*filepaths}/{*html}")
public String deliverHtml2(HttpServletResponse response, HttpServletRequest request) {
return doSomeMagicStuff();
}
Unfortunately the above code and some other variations do not work
This code worked fine for me. Notice, that for your purposes you can use just ant-patterns (instead of real regexp).
#RestController
#RequestMapping("filter-content")
public class FilterController {
#PostMapping(value = "*.xml")
public void onlyXml() {
System.out.println("Xml content");
}
#PostMapping(value = "*.html")
public void onlyHtml() {
System.out.println("Html content");
}
#PostMapping(value = "*")
public void otherContent() {
System.out.println("Other content");
}
}
P.S.: Also consider using of POST requests for "delivering" content.
In the end I was not finding an answer to the original question so I used this
#RequestMapping(value = "**")
To catch all request that were not handeled by a dedicated mapping and then I did the split of HTML and non HTML in plain Java Code...
I have a simple method where I need to change the contentType to "text/plain" but when I am using Jboss EAP 6.2 it is ignored and the returned value is "application/json.
#RestController
#RequestMapping("/upload")
public class UploadController {
...
#RequestMapping(method = RequestMethod.POST)
public Result foo(MultipartFile arquivo, HttpServletResponse response) {
response.setContentType("text/plain");
return uploadService.saveFile(arquivo);
}
}
I am using SpringMVC (rest controller) and Jackson to parse to JSON. The exactly same method works fine when I use Jetty instead of Jboss.
I can't use #RequestMapping(produces="text/plain") because the actual method has a bit more logic into it and sometimes it will return application/json others text/plain, depending if the request came from IE9 browser or not.
Can someone please tell me how can I set the contentType on Jboss?
Unfortunately the only way I could get this working for Jboss was creating a different method for IE9:
#RequestMapping(value = "/ie9", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
public String fooIE9(#RequestParam("arquivo") MultipartFile arquivo) throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer();
return ow.writeValueAsString(uploadService.saveFile(arquivo));
}
I'm developing a small JAX-RS application with Resteasy. I wanted the application to serve some static content for Javascript and CSS files, etc. and I would like to take advantage of the already gzipped version of the resources packaged in the jars of webjars.org. Thus, I need to handle the Accept-Encoding header and check if the .gz is there (or not).
So far, what I have is:
#Path("res/{path:.*}")
#GET
public Response webjars(#PathParam("path") String path, #HeaderParam("Accept-Encoding") String acceptEncoding) {
// Guesses MIME type from the path extension elsewhere.
String mime = mimes.getContentType(path);
if (acceptEncoding.contains("gzip")) {
InputStream is = getClass().getResourceAsStream("/META-INF/resources/webjars/" + path + ".gz");
if (is != null)
return Response.ok().type(mime).encoding("gzip").entity(is).build();
}
InputStream is = getClass().getResourceAsStream("/META-INF/resources/webjars/" + path);
if (is != null)
return Response.ok().type(mime).entity(is).build();
return Response.status(Status.NOT_FOUND).build();
}
But it doesn't work. The content served is totally broken. So far, I've found that a component that compresses the stream again: org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor because I manually filled the Content-Encoding header (using the ResponseBuilder.encoding method).
This looks like a bug to me because, apparently, there's no way to share an already gzipped stream. However, Is this achievable using JAX-RS? Is this a Resteasy bug?
I can think of a variety of ways to achieve the same thing externally to Resteasy, like mapping the webjars.org servlet (I'm not in a Servlet API 3.0 environment, so I have no META-INF/resources/ automatic classpath mapping). Nevertheless, my questions still prevail. It applies to several other scenarios.
Update:
For the record I have filled the issue RESTEASY-1170.
Here's an example implementation of my above comment.
The point I'm getting at is that if you don't want it to be handle by the current interceptor, don't set the header, create an Interceptor that will be name binded, with your own annotation, and set the priority to one lower than the one you want to avoid, then set the header in your Interceptor...
#AlreadyGzipped
#NameBinding
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface AlreadyGzipped {}
WriterInterceptor. Notice the #Priority. The GZIPEncodingInterceptor uses Priorities.ENTITY_CODER
#Provider
#AlreadyGzipped
#Priority(Priorities.ENTITY_CODER + 1000)
public class AlreadyGzippedWriterInterceptor implements WriterInterceptor {
#Context HttpHeaders headers;
#Override
public void aroundWriteTo(WriterInterceptorContext wic) throws IOException,
WebApplicationException {
String header = headers.getHeaderString("Accept-Encoding");
if (null != header && header.equalsIgnoreCase("gzip")) {
wic.getHeaders().putSingle("Content-Encoding", "gzip");
}
wic.proceed();
}
}
Test resource
#Path("resource")
public class AlreadyGzippedResoure {
#GET
#AlreadyGzipped
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getAlreadGzipped() throws Exception {
InputStream is = getClass().getResourceAsStream("/stackoverflow.png.gz");
return Response.ok(is).build();
}
}
Test
public class Main {
public static void main(String[] args) throws Exception {
Client client = ClientBuilder.newClient();
String url = "http://localhost:8080/api/resource";
Response response = client.target(url).request().acceptEncoding("gzip").get();
Image image = ImageIO.read(response.readEntity(InputStream.class));
JOptionPane.showMessageDialog(null,new JLabel(new ImageIcon(image)));
}
}
Result
I try to have a redirect with 301 Status Code (you know I want to be SEO friendly etc).
I do use InternalResourceViewResolver so I wanted to use some kind of a code similar to return "redirect:http://google.com" in my Controller.
This though would send a 302 Status Code
What I have tried is using a HttpServletResponse to set header
#RequestMapping(value="/url/{seo}", method = RequestMethod.GET)
public String detail(#PathVariable String seo, HttpServletResponse response){
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return "redirect:http://google.com";
}
It does still return 302.
After checking documentation and Google results I've come up with the following:
#RequestMapping(value="/url/{seo}", method = RequestMethod.GET)
public ModelAndView detail(#PathVariable String seo){
RedirectView rv = new RedirectView();
rv.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
rv.setUrl("http://google.com");
ModelAndView mv = new ModelAndView(rv);
return mv;
}
It does work perfectly fine and as expected, returning code 301
I would like to achieve it without using ModelAndView (Maybe it's perfectly fine though). Is it possible?
NOTE: included snippets are just parts of the detail controller and redirect does happen only in some cases (supporting legacy urls).
I would suggest using redirectView of spring like you have it. You have to have a complete URL including the domain etc for that to work, else it will do a 302. Or if you have access to HttpServletResponse, then you can do the below as below.
public void send301Redirect(HttpServletResponse response, String newUrl) {
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", newUrl);
response.setHeader("Connection", "close");
}
Not sure when it was added, but at least on v4.3.7 this works. You set an attribute on the REQUEST and the spring View code picks it up:
#RequestMapping(value="/url/{seo}", method = RequestMethod.GET)
public String detail(#PathVariable String seo, HttpServletRequest request){
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, HttpStatus.MOVED_PERMANENTLY);
return "redirect:http://google.com";
}
If you already return a ModelAndView and don't want to use HttpServletResponse, you can use this snippet:
RedirectView rv = new RedirectView("redirect:" + myNewURI);
rv.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
return new ModelAndView(rv);
I have a method;
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(ObjectOne objectOne, ObjectTwo objectTwo)
Now I know I can post a single object in json format, just putting it into the body.
But is it possible to do multiple objects? If so, how?
You can not use your method like this as correctly stated by Tarlog.
However, you can do this:
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects)
or this:
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(BeanWithObjectOneAndObjectTwo containerObject)
Furthermore, you can always combine your method with GET parameters:
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects, #QueryParam("objectTwoId") long objectTwoId)
The answer is no.
The reason is simple: This about the parameters you can receive in a method. They must be related to the request. Right? So they must be either headers or cookies or query parameters or matrix parameters or path parameters or request body. (Just to tell the complete story there is additional types of parameters called context).
Now, when you receive JSON object in your request, you receive it in a request body. How many bodies the request may have? One and only one. So you can receive only one JSON object.
If we look at what the OP is trying to do, he/she is trying to post two (possibly unrelated) JSON objects. First any solution to try and send one part as the body, and one part as some other param, IMO, are horrible solutions. POST data should go in the body. It's not right to do something just because it works. Some work-arounds might be violating basic REST principles.
I see a few solutions
Use application/x-www-form-urlencoded
Use Multipart
Just wrap them in a single parent object
1. Use application/x-www-form-urlencoded
Another option is to just use application/x-www-form-urlencoded. We can actually have JSON values. For examle
curl -v http://localhost:8080/api/model \
-d 'one={"modelOne":"helloone"}' \
-d 'two={"modelTwo":"hellotwo"}'
public class ModelOne {
public String modelOne;
}
public class ModelTwo {
public String modelTwo;
}
#Path("model")
public class ModelResource {
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String post(#FormParam("one") ModelOne modelOne,
#FormParam("two") ModelTwo modelTwo) {
return modelOne.modelOne + ":" + modelTwo.modelTwo;
}
}
The one thing we need to get this to work is a ParamConverterProvider to get this to work. Below is one that has been implemented by Michal Gadjos of the Jersey Team (found here with explanation).
#Provider
public class JacksonJsonParamConverterProvider implements ParamConverterProvider {
#Context
private Providers providers;
#Override
public <T> ParamConverter<T> getConverter(final Class<T> rawType,
final Type genericType,
final Annotation[] annotations) {
// Check whether we can convert the given type with Jackson.
final MessageBodyReader<T> mbr = providers.getMessageBodyReader(rawType,
genericType, annotations, MediaType.APPLICATION_JSON_TYPE);
if (mbr == null
|| !mbr.isReadable(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE)) {
return null;
}
// Obtain custom ObjectMapper for special handling.
final ContextResolver<ObjectMapper> contextResolver = providers
.getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);
final ObjectMapper mapper = contextResolver != null ?
contextResolver.getContext(rawType) : new ObjectMapper();
// Create ParamConverter.
return new ParamConverter<T>() {
#Override
public T fromString(final String value) {
try {
return mapper.reader(rawType).readValue(value);
} catch (IOException e) {
throw new ProcessingException(e);
}
}
#Override
public String toString(final T value) {
try {
return mapper.writer().writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new ProcessingException(e);
}
}
};
}
}
If you aren't scanning for resource and providers, just register this provider, and the above example should work.
2. Use Multipart
One solution that no one has mentioned, is to use multipart. This allows us to send arbitrary parts in a request. Since each request can only have one entity body, multipart is the work around, as it allows to have different parts (with their own content types) as part of the entity body.
Here is an example using Jersey (see official doc here)
Dependency
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey-2.x.version}</version>
</dependency>
Register the MultipartFeature
import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
#ApplicationPath("/api")
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
packages("stackoverflow.jersey");
register(MultiPartFeature.class);
}
}
Resource class
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.media.multipart.FormDataParam;
#Path("foobar")
public class MultipartResource {
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response postFooBar(#FormDataParam("foo") Foo foo,
#FormDataParam("bar") Bar bar) {
String response = foo.foo + "; " + bar.bar;
return Response.ok(response).build();
}
public static class Foo {
public String foo;
}
public static class Bar {
public String bar;
}
}
Now the tricky part with some clients is that there isn't a way to set the Content-Type of each body part, which is required for the above to work. The multipart provider will look up message body reader, based on the type of each part. If it's not set to application/json or a type, the Foo or Bar has a reader for, this will fail. We will use JSON here. There's no extra configuration but to have a reader available. I'll use Jackson. With the below dependency, no other configuration should be required, as the provider will be discovered through classpath scanning.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey-2.x.version}</version>
</dependency>
Now the test. I will be using cURL. You can see I explicitly set the Content-Type for each part with type. The -F signifies to different part. (See very bottom of the post for an idea of how the request body actually looks.)
curl -v -X POST \
-H "Content-Type:multipart/form-data" \
-F "bar={\"bar\":\"BarBar\"};type=application/json" \
-F "foo={\"foo\":\"FooFoo\"};type=application/json" \
http://localhost:8080/api/foobar
Result: FooFoo; BarBar
The result is exactly as we expected. If you look at the resource method, all we do is return this string foo.foo + "; " + bar.bar, gathered from the two JSON objects.
You can see some examples using some different JAX-RS clients, in the links below. You will also see some server side example also from those different JAX-RS implementations. Each link should have somewhere in it a link to the official documentation for that implementation
Jersey example
Resteasy example
CXF example
There are other JAX-RS implementations out there, but you will need to find the documentation for it yourself. The above three are the only ones I have experience with.
As far as Javascript clients, most of the example I see (e.g. some of these involve setting the Content-Type to undefined/false (using FormData), letting the Browser handle the it. But this will not work for us, as the Browser will not set the Content-Type for each part. And the default type is text/plain.
I'm sure there are libraries out there that allow setting the type for each part, but just to show you how it can be done manually, I'll post an example (got a little help from here. I'll be using Angular, but the grunt work of building the entity body will be plain old Javascript.
<!DOCTYPE html>
<html ng-app="multipartApp">
<head>
<script src="js/libs/angular.js/angular.js"></script>
<script>
angular.module("multipartApp", [])
.controller("defaultCtrl", function($scope, $http) {
$scope.sendData = function() {
var foo = JSON.stringify({foo: "FooFoo"});
var bar = JSON.stringify({bar: "BarBar"});
var boundary = Math.random().toString().substr(2);
var header = "multipart/form-data; charset=utf-8; boundary=" + boundary;
$http({
url: "/api/foobar",
headers: { "Content-Type": header },
data: createRequest(foo, bar, boundary),
method: "POST"
}).then(function(response) {
$scope.result = response.data;
});
};
function createRequest(foo, bar, boundary) {
var multipart = "";
multipart += "--" + boundary
+ "\r\nContent-Disposition: form-data; name=foo"
+ "\r\nContent-type: application/json"
+ "\r\n\r\n" + foo + "\r\n";
multipart += "--" + boundary
+ "\r\nContent-Disposition: form-data; name=bar"
+ "\r\nContent-type: application/json"
+ "\r\n\r\n" + bar + "\r\n";
multipart += "--" + boundary + "--\r\n";
return multipart;
}
});
</script>
</head>
<body>
<div ng-controller="defaultCtrl">
<button ng-click="sendData()">Send</button>
<p>{{result}}</p>
</div>
</body>
</html>
The interesting part is the createRequest function. This is where we build the multipart, setting the Content-Type of each part to application/json, and concatenating the stringified foo and bar objects to each part. If you are unfamiliar multipart format see here for more info. The other interesting part is the header. We set it to multipart/form-data.
Below is the result. In Angular I just used the result to show in the HTML, with $scope.result = response.data. The button you see was just to make the request. You will also see the request data in firebug
3. Just wrap them in a single parent object
This option should be self explanatory, as others have already mentioned.
The next approach is usually applied in this kind of cases:
TransferObject {
ObjectOne objectOne;
ObjectTwo objectTwo;
//getters/setters
}
#POST
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
public void test(TransferObject object){
// object.getObejctOne()....
}
You can't put two separate objects in one single POST call as explained by Tarlog.
Anyway you could create a third container object that contains the first two objects and pass that one within the POS call.
I have also faced with these problem. Maybe this will help.
#POST
#Path("/{par}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Object centralService(#PathParam("par") String operation, Object requestEntity) throws JSONException {
ObjectMapper objectMapper=new ObjectMapper();
Cars cars = new Cars();
Seller seller = new Seller();
String someThingElse;
HashMap<String, Object> mapper = new HashMap<>(); //Diamond )))
mapper = (HashMap<String, Object>) requestEntity;
cars=objectMapper.convertValue(mapper.get("cars"), Cars.class);
seller=objectMapper.convertValue(mapper.get("seller"), Seller.class);
someThingElse=objectMapper.convertValue(mapper.get("someThingElse"), String.class);
System.out.println("Cars Data "+cars.toString());
System.out.println("Sellers Data "+seller.toString());
System.out.println("SomeThingElse "+someThingElse);
if (operation.equals("search")) {
System.out.println("Searching");
} else if (operation.equals("insertNewData")) {
System.out.println("Inserting New Data");
} else if (operation.equals("buyCar")) {
System.out.println("Buying new Car");
}
JSONObject json=new JSONObject();
json.put("result","Works Fine!!!");
return json.toString();
}
*******CARS POJO********#XmlRootElement for Mapping Object to XML or JSON***
#XmlRootElement
public class Cars {
private int id;
private String brand;
private String model;
private String body_type;
private String fuel;
private String engine_volume;
private String horsepower;
private String transmission;
private String drive;
private String status;
private String mileage;
private String price;
private String description;
private String picture;
private String fk_seller_oid;
} // Setters and Getters Omitted
*******SELLER POJO********#XmlRootElement for Mapping Object to XML or JSON***
#XmlRootElement
public class Seller {
private int id;
private String name;
private String surname;
private String phone;
private String email;
private String country;
private String city;
private String paste_date;
}//Setters and Getters omitted too
*********************FRONT END Looks Like This******************
$(function(){
$('#post').on('click',function(){
console.log('Begins');
$.ajax({
type:'POST',
url: '/ENGINE/cars/test',
contentType: "application/json; charset=utf-8",
dataType: "json",
data:complexObject(),
success: function(data){
console.log('Sended and returned'+JSON.stringify(data));
},
error: function(err){
console.log('Error');
console.log("AJAX error in request: " + JSON.stringify(err, null, 2));
}
}); //-- END of Ajax
console.log('Ends POST');
console.log(formToJSON());
}); // -- END of click function POST
function complexObject(){
return JSON.stringify({
"cars":{"id":"1234","brand":"Mercedes","model":"S class","body_type":"Sedan","fuel":"Benzoline","engine_volume":"6.5",
"horsepower":"1600","transmission":"Automat","drive":"Full PLag","status":"new","mileage":"7.00","price":"15000",
"description":"new car and very nice car","picture":"mercedes.jpg","fk_seller_oid":"1234444"},
"seller":{ "id":"234","name":"Djonotan","surname":"Klinton","phone":"+994707702747","email":"email#gmail.com", "country":"Azeribaijan","city":"Baku","paste_date":"20150327"},
"someThingElse":"String type of element"
});
} //-- END of Complex Object
});// -- END of JQuery - Ajax
It can be done by having the POST method declared to accept array of objects. Example like this
T[] create(#RequestBody T[] objects) {
for( T object : objects ) {
service.create(object);
}
}
Change #Consumes(MediaType.APPLICATION_JSON)
to #Consumes({MediaType.APPLICATION_FORM_URLENCODED})
Then you can putting multiple objects into the body
My solution is written for CXF, it appears to be quite simple.
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
#POST
#Path("paramTest")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public GenericResult paramTest(
#Multipart(value = "myData", type=MediaType.APPLICATION_JSON)
ObjectOne myData,
#Multipart(value = "infoList", type=MediaType.APPLICATION_JSON)
ObjectTwo[] infoList);
The test code for this with io.restassurred:
#Test
public void paramTest()
{
String payload1 = "" +
"{ \"name\": \"someName\", \"branch\": \"testBranch\" }";
String payload2 =
" [ { \"name\": \"cn\", \"status\": \"ts\" }," +
"{ \"name\": \"cn2\", \"status\": \"ts2\" } ] ]";
RestAssured.
given().
contentType("multipart/form-data").
multiPart("myData", payload1, "application/json").
multiPart("infoList", payload2, "application/json").
post(String.format("%s/paramTest", API_PATH)).
then().
statusCode(HttpStatus.SC_OK).
contentType(ContentType.JSON).
body("success", Matchers.equalTo(true));
}