I am trying to carry JSON data in an HTTP GET request message, but my Spring MVC server can't seem to retrieve the JSON data from the GET request body.
HTTP's GET method does not include a request body as part of the spec. Spring MVC respects the HTTP specs. Specifically, servers are allowed to discard the body. The request URI should contain everything needed to formulate the response.
If you need a request body, change the request type to POST, which does include the request body.
Based on official info
https://docs.spring.io/spring-framework/docs/4.1.0.RC2/spring-framework-reference/html/mvc.html
#RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader"));
byte[] requestBody = requestEntity.getBody();
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
In case anyone's here trying to get the OpenAPI generation to treat the fields of the request object as separate GET params, you'll want to use #ParameterObject (org.springdoc.api.annotations.ParameterObject) which was added here: https://github.com/springdoc/springdoc-openapi/issues/590
Related
I want to send a post request using ballerina to get a access token from the Choreo Dev Portal. I am able to do it using postman. But unable to make it work in Ballerina code level. it gives 415 - unsupported media type error. Need some Help in Ballerina
import ballerina/http;
import ballerina/io;
import ballerina/url;
public function main() returns error? {
final http:Client clientEndpoint = check new ("https://sts.choreo.dev");
http:Request request = new();
string payload = string`grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=*******&
subject_token_type=urn:ietf:params:oauth:token-type:jwt&
requested_token_type=urn:ietf:params:oauth:token-type:jwt`;
string encodedPayload = check url:encode(payload, "UTF-8");
io:print(encodedPayload);
request.setTextPayload(encodedPayload);
request.addHeader("Authorization","Basic *****");
request.addHeader("Content-Type","application/x-www-form-urlencoded");
io:print(request.getTextPayload());
json resp = check clientEndpoint->post("/oauth2/token",request);
io:println(resp.toJsonString());
}
I was expecting an access token from Choreo Devportal for the particular application.
import ballerina/http;
import ballerina/io;
import ballerina/mime;
public function main() returns error? {
// Creates a new client with the backend URL.
final http:Client clientEndpoint = check new ("https://sts.choreo.dev");
json response = check clientEndpoint->post("/oauth2/token",
{
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"requested_token_type":"urn:ietf:params:oauth:token-type:jwt",
"subject_token":"****"
},
{
"Authorization": "Basic ****"
},
mime:APPLICATION_FORM_URLENCODED
);
io:println(response.toString());
}
This is the recommended way to send the post request with the form URL encoded payload.
Change the Content-type header setting method from addHeader() to setHeader().
The request.setTextPayload(encodedPayload); will set the Content-type as text/plain as the default content type header.
Later request.addHeader("Content-Type","application/x-www-form-urlencoded"); is executed. The addHeader() method will append the new value to the same header in addition to the previously added text/plain. But the setHeader() will replace the previously set header which is the correct way in this scenario.
However better way is to pass the Content-type as the second param of setXXXPayload() method.
request.setTextPayload(encodedPayload, "application/x-www-form-urlencoded");
RequestLogHandler requestLogHandler = new RequestLogHandler();
Slf4jRequestLog requestLog = new CustomSlf4jRequestLog();
requestLogHandler.setRequestLog(requestLog);
Slf4jRequestLog is only logging request method, url and date, and response status code and bytes written.
I definitely want to log body for my PUT/POST requests.
I derived CustomSlf4jRequestLog from Slf4jRequestLog and I tried:
public void log(Request request, Response response) {
StringBuilder sb = new StringBuilder("RequestBody: ");
try {
LOG.info("BODY SIZE: " + request.getContentLength());
BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
LOG.info(sb.toString());
Unfortunately no body is printed out as it is already processed by handler?
Is it possible to get body of request here?
(I really care about body because I have JsonProvider and I want to see a whole body of request when Json fails to parse data) Or when my app fails I want to see what caused that without adding logging for each input request.
Servlet spec getting in your way here.
The actual servlet that is processing the request has already read the request body, rending further reads from request.getInputStream() invalid (you are at EOF)
If you want to capture the request body, you'll need to create a custom Servlet Filter, and then wrap the HttpServletRequest, overriding the getInputStream() AND getReader() methods with your own implementations that make a copy of the data that was read.
Then its up to you to determine what you want to do with that copy of the request body content.
or
You can just use a network capture utility like Wireshark to see what the request body was. Even if you use HTTPS, you can configure Wireshark with your server certificate to inspect encrypted conversation.
The logger is calling getInputStream() on the same request again. You are not allowed to read the same data twice. Yo should create a ServletRequestWrapper to make a copy of the body of the request.
I feel like this should be easy. I have an app where all I am trying to do is have a form page (index.jsp) that calls a servlet (CheckInfo.java) which sets a new header (myHeader) and redirects the user to another page (redirect.jsp). All of these files are on the same server. The index.jsp is sending the request just fine and CheckInfo is processing and redirecting, but myHeader is not showing up on redirect.jsp. I've read several posts talking about response.sendRedirect sends a 302 which doesn't pass headers and that I should use RequestDispatcher, but nothing seems to work. Is there no way to send headers from a servlet to a jsp?
Here is the servlet code:
response.setHeader("myHeader", "hey there");
response.sendRedirect("redirect.jsp");
I have also tried this:
response.setHeader("myHeader", "hey there");
RequestDispatcher view = request.getRequestDispatcher("redirect.jsp");
view.forward(request, response);
And I have this in redirect.jsp:
System.out.println(request.getHeader("myHeader"));
This does not print anything.
If the answer to my question is no... then I would settle for a way to set the header once I got back to the jsp. My reverse proxy is looking for a specific header to determine whether or not to perform an action. Obviously I tried response.addHeader() on redirect.jsp, but the page has already loaded at that point so that just made me feel dumb.
response.setHeader("myHeader", "hey there");
response.sendRedirect("redirect.jsp");
You are adding it as response header and it is 302 response. Browser on seeing a 302 response will just look for Location header and fire a new request to this location. Custom headers in the response are untouched whereas you are expecting these custom response headers to be included in the request (to new redirect location) which is not being sent.
Solution:-
1. you can use request dispatcher and forward the request instead of external redirect. And you need to use request attributes here.
2. you can call submit form using an ajax request may be jquery like and handle the response manually(for 302 response) but would not suggest you to use this approach as it is not a cleaner and intuitive approach. Just mentioning so that you know there are other ways to achieve this.
The problem is that the redirect() method of the response initiates a new request altogether, thereby loosing the attributes that were set before redirecting. Luckily there is a fluent way of solving the problem still. See below
response.setHeader("myHeader", "hey there");
request.getRequestDispatcher("redirect.jsp").forward(request, response);
Then in your destination you can do response.getHeaders("myHeader")
I have tested the code.
I hope it's clear that in case of asking the client to redirect to another URL - the browser shall not honor the cookies.
However, the 2nd method - where server forwards the request is feasible. The main mistake appears to be in mutating the response while we are supposed to change the request.
Then again, one cannot directly mutate a HttpServletRequest object. Here is one way to do so:
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request){
public String getHeader(String name) {
String value = super.getHeader(name);
if(Strings.isNullOrEmpty(value)) {
...
value = myNewHeader;
}
return value;
}
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if(values.size()==0) {
...
values.add(myNewHeader);
}
return Collections.enumeration(values);
}
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
names.add(myNewHeaderName);
...
return Collections.enumeration(names);
}
}
Followed by:
RequestDispatcher view = request.getRequestDispatcher("redirect.jsp");
// OR (If you can get servletContext)
RequestDispatcher view = servletContext.getRequestDispatcher("redirect.jsp");
view.forward(requestWrapper, response);
Reference:
https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequestWrapper.html
For the headers case - getHeader(), getHeaders() and getHeaderNames() fn in the reqWrapper obj need Overriding.
Similarly you can override cookies and params.
See also: Modify request parameter with servlet filter
NOTE: It might not be possible to forward a req to an endpoint which expects a different MIME type.
A client side redirect creates a new HTTP request/response pair.
This link may help you more on debugging perspective -
Sending Custom headers
In my server handler;
- channelRead() always gets the msg as HTTPRequest and there I cannot find any place to get the POST request payload.
Then I tried following in my handler to check if it works. The decoder has 0 elements.
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), request);
In my server pipeline I have HttpServerCodec and a custom handler only.
It's likely that your HTTP request is chunked. You should try to add an io.netty.handler.codec.http.HttpObjectAggregator to your pipeline, just after the codecs. It will give a FullHttpRequest to your handler.
ChannelPipeline p = ...;
...
p.addLast("encoder", new HttpResponseEncoder());
p.addLast("decoder", new HttpRequestDecoder());
p.addLast("aggregator", new HttpObjectAggregator(1048576));
...
p.addLast("handler", new MyServerHandler());
Alternatively, you could check this example where HttpRequest and HttpContent are handled separately.
As Leo Gomes mentioned, HTTP Request maybe chunked. so add HttpObjectAggregator before your own handler in pipeline.
if HTTP POST request body is Simple Json String. You can parse it in your own handler like this:
private String parseJosnRequest(FullHttpRequest request){
ByteBuf jsonBuf = request.content();
String jsonStr = jsonBuf.toString(CharsetUtil.UTF_8);
return jsonStr;
}
I have added the HttpPutFormContentFilter in web.xml.
Here is an action for reveiving both GET,POST,DELETE and PUT method.
#RequestMapping(value = "/**")
public ResponseEntity<byte[]> proxy(HttpServletRequest request,#RequestParam MultiValueMap<String, String> params, #RequestBody byte[] body, #RequestHeader MultiValueMap<String, String> headers) {
When I use POST and add the application/x-www-form-urlencoded header,I can get both the request body and request param.
When I use PUT and add the application/x-www-form-urlencoded header,I can get the request param,but I can't get request body!
There is any bug in the HttpPutFormContentFilter?
According to the Servlet specification (see chapters 1.1 and 3.1.1), when you receive a POST request and the content type is application/x-www-form-urlencoded, that form data needs to be made available through the HttpServletRequest#getParameterXXX() methods. This is not true for PUT requests.
In all cases, the body of the HttpServletRequest is available as an InputStream to Servlet and Filter instances.
For POST, when Spring sees
#RequestParam MultiValueMap<String, String> params
it uses a RequestParamMapMethodArgumentResolver to resolve the argument. This reads directly from the HttpServletRequest parameter map. When Spring sees
#RequestBody byte[] body
it uses a RequestResponseBodyMethodProcessor which reads from the HttpServletRequest InputStream using a ByteArrayHttpMessageConverter to fill up a byte[].
Once you've read the HttpServletRequest InputStream, you cannot re-read (in the default configuration).
For PUT, because the Servlet container doesn't store form parameters in the HttpServletRequest for PUT requests, Spring decided to introduce the HttpPutFormContentFilter. This Filter reads the HttpServletRequest body to populate its own parameter map in an HttpServletRequestWrapper which it hands you.
Once this is done, the request parameters are available to the RequestParamMapMethodArgumentResolver, but when the RequestResponseBodyMethodProcessor tries to fill up the byte[], there are no bytes left in the InputStream so it leaves it empty.
One workaround is to create your own Filter (which must execute before the HttpPutFormContentFilter, so it's kind of bad practice), which wraps the HttpServletRequest in a HttpServletRequestWrapper which buffers the InputStream in a ByteArrayInputStream so you can re-read it as many times as necessary.