Reading HTTP headers in a Spring REST controller - java

I am trying to read HTTP headers in Spring based REST API. I followed this. But I am getting this error:
No message body reader has been found for class java.lang.String,
ContentType: application/octet-stream
I am new to Java and Spring so can't figure this out.
This is how my call looks like:
#WebService(serviceName = "common")
#Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public interface CommonApiService {
#GET
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Produces(MediaType.APPLICATION_JSON)
#Path("/data")
public ResponseEntity<Data> getData(#RequestHeader(value="User-Agent") String userAgent, #DefaultValue ("") #QueryParam("ID") String id);
}
I have tried #Context: HTTPHeader is null in this case.
How to get values from HTTP headers?

The error that you get does not seem to be related to the RequestHeader.
And you seem to be confusing Spring REST services with JAX-RS, your method signature should be something like:
#RequestMapping(produces = "application/json", method = RequestMethod.GET, value = "data")
#ResponseBody
public ResponseEntity<Data> getData(#RequestHeader(value="User-Agent") String userAgent, #RequestParam(value = "ID", defaultValue = "") String id) {
// your code goes here
}
And your REST class should have annotations like:
#Controller
#RequestMapping("/rest/")
Regarding the actual question, another way to get HTTP headers is to insert the HttpServletRequest into your method and then get the desired header from there.
Example:
#RequestMapping(produces = "application/json", method = RequestMethod.GET, value = "data")
#ResponseBody
public ResponseEntity<Data> getData(HttpServletRequest request, #RequestParam(value = "ID", defaultValue = "") String id) {
String userAgent = request.getHeader("user-agent");
}
Don't worry about the injection of the HttpServletRequest because Spring does that magic for you ;)

I'm going to give you an example of how I read REST headers for my controllers. My controllers only accept application/json as a request type if I have data that needs to be read. I suspect that your problem is that you have an application/octet-stream that Spring doesn't know how to handle.
Normally my controllers look like this:
#Controller
public class FooController {
#Autowired
private DataService dataService;
#RequestMapping(value="/foo/", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<Data> getData(#RequestHeader String dataId){
return ResponseEntity.newInstance(dataService.getData(dataId);
}
Now there is a lot of code doing stuff in the background here so I will break it down for you.
ResponseEntity is a custom object that every controller returns. It contains a static factory allowing the creation of new instances. My Data Service is a standard service class.
The magic happens behind the scenes, because you are working with JSON, you need to tell Spring to use Jackson to map HttpRequest objects so that it knows what you are dealing with.
You do this by specifying this inside your <mvc:annotation-driven> block of your config
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
ObjectMapper is simply an extension of com.fasterxml.jackson.databind.ObjectMapper and is what Jackson uses to actually map your request from JSON into an object.
I suspect you are getting your exception because you haven't specified a mapper that can read an Octet-Stream into an object, or something that Spring can handle. If you are trying to do a file upload, that is something else entirely.
So my request that gets sent to my controller looks something like this simply has an extra header called dataId.
If you wanted to change that to a request parameter and use #RequestParam String dataId to read the ID out of the request your request would look similar to this:
contactId : {"fooId"}
This request parameter can be as complex as you like. You can serialize an entire object into JSON, send it as a request parameter and Spring will serialize it (using Jackson) back into a Java Object ready for you to use.
Example In Controller:
#RequestMapping(value = "/penguin Details/", method = RequestMethod.GET)
#ResponseBody
public DataProcessingResponseDTO<Pengin> getPenguinDetailsFromList(
#RequestParam DataProcessingRequestDTO jsonPenguinRequestDTO)
Request Sent:
jsonPengiunRequestDTO: {
"draw": 1,
"columns": [
{
"data": {
"_": "toAddress",
"header": "toAddress"
},
"name": "toAddress",
"searchable": true,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "fromAddress",
"header": "fromAddress"
},
"name": "fromAddress",
"searchable": true,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "customerCampaignId",
"header": "customerCampaignId"
},
"name": "customerCampaignId",
"searchable": true,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "penguinId",
"header": "penguinId"
},
"name": "penguinId",
"searchable": false,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "validpenguin",
"header": "validpenguin"
},
"name": "validpenguin",
"searchable": true,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "",
"header": ""
},
"name": "",
"searchable": false,
"orderable": false,
"search": {
"value": "",
"regex": false
}
}
],
"order": [
{
"column": 0,
"dir": "asc"
}
],
"start": 0,
"length": 10,
"search": {
"value": "",
"regex": false
},
"objectId": "30"
}
which gets automatically serialized back into an DataProcessingRequestDTO object before being given to the controller ready for me to use.
As you can see, this is quite powerful allowing you to serialize your data from JSON to an object without having to write a single line of code. You can do this for #RequestParam and #RequestBody which allows you to access JSON inside your parameters or request body respectively.
Now that you have a concrete example to go off, you shouldn't have any problems once you change your request type to application/json.

Instead of taking the HttpServletRequest object in every method, keep in controllers' context by auto-wiring via the constructor. Then you can access from all methods of the controller.
public class OAuth2ClientController {
#Autowired
private OAuth2ClientService oAuth2ClientService;
private HttpServletRequest request;
#Autowired
public OAuth2ClientController(HttpServletRequest request) {
this.request = request;
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> createClient(#RequestBody OAuth2Client client) {
System.out.println(request.getRequestURI());
System.out.println(request.getHeader("Content-Type"));
return ResponseEntity.ok();
}
}

Related

Openapi generator angular not generating multipart formdata endpoint correctly. (useForm is false by default)

This is the endpoint I have:
#PostMapping(value = "/file-upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<FileReference> handleFileUpload(
#RequestPart(value = "file", name = "file") MultipartFile[] file, #ApiIgnore HttpSession session) {
return service.handleFileUpload(
Arrays.stream(file).map(MultipartFileWithUUID::new).collect(Collectors.toList()),
session);
}
This is the generated endpoint in the swagger.json (swagger 2.0):
...
"post": {
"tags": [
"damage-report-controller"
],
"summary": "handleFileUpload",
"operationId": "handleFileUploadUsingPOST",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"parameters": [
{
"name": "file",
"in": "formData",
"required": false,
"type": "array",
"items": {
"type": "file"
},
"collectionFormat": "multi"
}
],
...
And here is the generated function:
public handleFileUploadUsingPOST(file?: Array<Blob> ...) {
let headers = this.defaultHeaders;
header settings etc...
// to determine the Content-Type header
const consumes: string[] = [
'multipart/form-data'
];
const canConsumeForm = this.canConsumeForm(consumes);
let formParams: { append(param: string, value: any): any; };
let useForm = false;
...
if (useForm) {
formParams = new FormData();
} else {
formParams = new HttpParams({encoder: this.encoder});
}
...
}
The error I have is 415: Unsupported media type.
I don't know how it should be generated correctly, but I changed let useForm; to true and it works,
so my guess that let useForm = canConsumeForm(consumes) because canConsumeForm returns a boolean.
What should I change so it gets generated correctly?
In case anyone reads this, I haven't found the proper solution using swagger 2.0, so I updated to openapi 3.0 and that fixed the problem.
Apparently swagger 2.0 doesn't support uploading an array of files, even though the only problem was that the generated service didn't use existing functions properly.

ExceptionMapper entity seems to get wrapped by Quarkus

In Quarkus, it seem that the entity returned by an exception mapper get wrapped in another entity.
Give an JAX-RS exception mapper like:
#Provider
public class WebhookExceptionMapper implements ExceptionMapper<WebhookException> {
#Override
public Response toResponse(final WebhookException e) {
return Response.status(e.getError().getCode().getStatus())
.entity(Entity.entity(e.getError(), MediaType.APPLICATION_JSON))
.build();
}
}
I get the following error response:
{
"entity": {
"code": "SOME_ERROR_CODE",
"msg": "Error message"
},
"variant": {
"language": null,
"mediaType": {
"type": "application",
"subtype": "json",
"parameters": {},
"wildcardType": false,
"wildcardSubtype": false
},
"encoding": null,
"languageString": null
},
"annotations": [],
"mediaType": {
"type": "application",
"subtype": "json",
"parameters": {},
"wildcardType": false,
"wildcardSubtype": false
},
"language": null,
"encoding": null
}
I would like the following to be returned:
{
"code": "SOME_ERROR_CODE",
"msg": "Error message"
}
Is that possible?
As is seen by looking at the package name, the javax.ws.rs.client.Entity class is only meant to be used on the client side. On the server side, you don't need to use it. What you are actually seeing is the Entity object being serialized, not the error.
If you want to set the type, just use the type() method on the Response.ResponseBuilder (that you get back from calling Response.status()). And to set the body just use the entity() method.
return Response.status(e.getError().getCode().getStatus())
.entity(e.getError())
.type(MediaType.APPLICATION_JSON)
.build();

How to set media type of a webservice response in springboot?

I created a web service and I'll call another web service from inside. According to response media type that call from inside, I return my real response.
But whatever I do, all response returned as JSON object.
My web service class is;
#RestController
#RequestMapping("/changeservicemode")
public class ChangeServiceMode {
#RequestMapping(method = RequestMethod.GET)
public Response changeMode(#RequestHeader(value = "serviceUrl") String serviceUrl,
#RequestHeader(value = "serviceMode") String serviceMode) {
IVirtualDocumentService docService = UtilsForSpring.getSingleBeanOfType(IVirtualDocumentService.class);
VirtualDocument documentByUrl = docService.findDocumentByVirtualUrl(serviceUrl);
String mediaType = MediaType.APPLICATION_XML;//I'll get media type from another response that will call above code in this point
if (documentByUrl == null) {
return Response.status(Status.NOT_FOUND).type(mediaType).entity("This url not found on DB!").build();
}
if (SimulationMode.LEARN.equalsIgnoreCase(serviceMode)) documentByUrl.setSimulationMode(SimulationMode.LEARN);
if (SimulationMode.SIMULATE.equalsIgnoreCase(serviceMode)) documentByUrl.setSimulationMode(SimulationMode.SIMULATE);
if (SimulationMode.STOP.equalsIgnoreCase(serviceMode)) documentByUrl.setSimulationMode(SimulationMode.STOP);
docService.save(documentByUrl);
String entity = "url: " + serviceUrl + ", mode: " + documentByUrl.getSimulationMode();
return Response.status(Status.OK).entity(entity).type(mediaType).build();
}
}
here is my response;
{
"context": {
"headers": {
"Content-Type": [
{
"type": "application",
"subtype": "xml",
"parameters": {},
"wildcardSubtype": false,
"wildcardType": false
}
]
},
"entity": "url: http://localhost:8066/virtual/wsapi/personelvirtual/getallpersonels, mode: SIMULATE",
"entityType": "java.lang.String",
"entityAnnotations": [],
"entityStream": {
"closed": false,
"committed": false
},
"length": -1,
"language": null,
"location": null,
"lastModified": null,
.
.
.
. continue..
Try to specify this property
produces = MediaType.APPLICATION_XML_VALUE
for the #RequestMapping you have set for changeMode method. According to the docs, it should do what you want.

Swagger ui - Query param

I am using Swagger ui and Swagger core (1.3) for a jersey application. I have certain query parameters which I must send with every request like post, get, delete...
How can I default this ?
You can use the annotation #ApiParam from the Swagger annotations in order to configure the Query param to be used from the Swagger-UI.
For example
#Path("/{username}")
#ApiOperation(value = "Updated user")
public Response updateUser(
#ApiParam(value = "description for query-parameter") #QueryParam("username") String username
) {
...
}
Please, read more about this annotation in the following official documentation:
https://github.com/swagger-api/swagger-core/wiki/Annotations#apiparam
You can't, but since swagger 2.0 (I don't know if this is supported by swagger-code/swagger-ui), you can defines parameters to be reuse across operations.
For example :
{
"parameters": {
"pageParam": {
"name": "page",
"in": "query",
"description": "page number to get",
"required": false,
"type": "integer",
"format": "int32"
}
},
"paths": {
"/customers": {
"get": {
"description": "Retrive list of customers",
"parameters": {
"$ref": "#/parameters/pageParam"
},
...
}
}
},
...
}
For swagger version 3, you can use the annotation #Parameter
import io.swagger.v3.oas.annotations.Parameter
https://docs.swagger.io/swagger-core/v2.0.0-RC3/apidocs/io/swagger/v3/oas/annotations/Parameter.html

How to set http header in Json response

I've a CXF RESTful service which returns both XML and Json format. I need to add a custom http header in the RESTful service. Here's a sample code snippet.
#GET
#Path("/test")
#Produces("application/xml")
public Response test(
#QueryParam("p") String var
{
TestRequest req = new TestRequest();
req.setVar(var);
TestResponse res = p.getData(req);
return Response.ok(res).header("Result", res.getResult()).build();
}
The above code shows the XML response which sets the custom http header "Result". I'm able to see the new http header in the response header. So far so good.
Now, here's the Json version which internally calls the testService() method to get the result, then use google Gson API to send the result back. This has been working well, till I decided to return the new header. Here's the code snippet.
#GET
#Path("/test/jsonp")
public String testJSONP(
#QueryParam("p") String var,
#QueryParam("cb") String callBack
{
Response resp = test(var);
XStream xs = new XStream(new JsonHierarchicalStreamDriver());
xs.setMode(XStream.NO_REFERENCES);
xs.alias("TestResponse", TestResponse.class);
StringBuilder sb = new StringBuilder();
sb.append(callBack);
sb.append("(");
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(XMLGregorianCalendar.class, new XMLGregorianCalenderSerializer());
gb.setPrettyPrinting();
Gson gson = gb.create();
sb.append(gson.toJson(resp));
sb.append(")");
return sb.toString();
}
I'm not able to see the http header in Json response.
Any feedback will be highly appreciated.
-Thanks
UPDATE
I added the following code in Json method for my testing.
#GET
#Path("/test/jsonp")
public String testJSONP(
#QueryParam("p") String var,
#QueryParam("cb") String callBack
{
Response resp = test(var);
XStream xs = new XStream(new JsonHierarchicalStreamDriver());
xs.setMode(XStream.NO_REFERENCES);
xs.alias("TestResponse", TestResponse.class);
StringBuilder sb = new StringBuilder();
sb.append(callBack);
sb.append("(");
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(XMLGregorianCalendar.class, new XMLGregorianCalenderSerializer());
gb.setPrettyPrinting();
Gson gson = gb.create();
sb.append(gson.toJson(resp));
sb.append(")");
return Response.ok(sb.toString(), MediaType.APPLICATION_JSON).header("Result", "50").build();
}
This sets the header value correctly,but the issue is the Json response format seems to have changed. Since this is an existing service, I'm not allowed to do that.
Here's the existing response format
null({
"status": "Completed",
"totalResult": "252",
"bin": [
{
"type": "source",
"value": "documentation",
"ndocs": "243"
},
{
"type": "source",
"value": "wikihelp",
"ndocs": "6"
},
"entries": {
"item": [
{
"url": "http://test.com/test.htm",
"title": "\u003cspan class\u003d\"vivbold qt0\"\u003eXREF\u003c/span\u003e",
"snippet": " Test data.",
"source": "documentation",
"type": "html",
"shortDescription": "Starts the TEST command.",
"category": [
"User"
],
"publishDate": "2012-02-05T12:00:00-0500",
"lastUpdateDate": "2012-03-14T12:00:00-0400",
"topicId": "GUID-7DD70C3C-B8AD-40F1-8A69-5D1EECEAB013"
}
]
}
})
Here's the response after adding this change
null({
"status": 200,
"entity": {
"status": "Completed",
"totalResult": "252",
"bin": [
{
"type": "source",
"value": "documentation",
"ndocs": "243"
},
{
"type": "source",
"value": "wikihelp",
"ndocs": "6"
}
],
"entries": {
"item": [
{
"url": "http://test.com/test.htm",
"title": "\u003cspan class\u003d\"vivbold qt0\"\u003eXREF\u003c/span\u003e",
"snippet": " Test data.",
"source": "documentation",
"type": "html",
"shortDescription": "Starts the TEST command.",
"category": [
"User"
],
"publishDate": "2012-02-05T12:00:00-0800",
"lastUpdateDate": "2012-03-14T12:00:00-0700",
"topicId": "GUID-7DD70C3C-B8AD-40F1-8A69-5D1EECEAB013"
}
]
}
},
"metadata": {
"Result": {
}
}
})
You need to change signature of your method, to return an instance of Response class, instead of a String, and then built the response manually.
From the CXF wiki page:
#Path("/example")
public ExampleResource {
#GET
public Response getSomething() {
return Response.ok(/* some entity */).header("CustomHeader", "CustomValue").build();
}
}
Update
You can also inject HttpServletResponse into your handler using #Context annotation like this:
#Path("/example")
public class Welcome {
#GET
public String getSomething(
#QueryParam("p1") String param1,
#QueryParam("p2") String param2,
#Context HttpServletResponse response) {
response.addHeader("CustomHeader", "CustomValue");
return "my awesome response";
}
}
Note, that there is a CXF-1498 bug in versions prior to 2.1 that causes HttpServletResponse not being injected, so you need a newer CXF version.

Categories

Resources