Response header "Cache-control" visible in PostMan - but not curl -i? - java

Getting some weird behavior here when using the following spring filter.
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.addHeader("cache-control", "no-store");
response.addHeader("pragma", "no-cache");
filterChain.doFilter(request, response);
}
It is configured in XML as well, and I know it works, because when I send a request, I am always able to see the "pragma" header in there, no matter what happens.
But the interesting thing here, is that when I hit this from curl - I get the following headers back :
Date: Wed, 19 Apr 2017 20:45:48 GMT
Pragma: no-cache
WWW-Authenticate: Basic realm="Spring Security Application"
Content-Type: text/html
Transfer-Encoding: chunked
Server: Jetty(9.2.10.v20150310)
But whenever I make the same request via a rest client (such as postman) I see the following headers : Image posted here because I am not allowed to embed images on stackoverflow just yet.
Any idea why this would happen?
EDIT FOR FURTHER INFORMATION : I also see both headers in the network section of developer tools if I hit this from a browser. No idea why it wont appear in curl.
ALSO, I cannot use an interceptor for this as management have decided they don't like them.

Related

Tomcat filter in web.xml gets triggered when sending OPTIONS from Postman but not when sending OPTIONS from browser or curl [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 years ago.
Improve this question
Tomcat (hosts my java back-end API) has this filter:
<filter>
<filter-name>ApiOriginFilter</filter-name>
<filter-class>io.swagger.api.ApiOriginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ApiOriginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
which is this java code (I added the System.out.println("Test"); part):
package io.swagger.api;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
#javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.JavaJerseyServerCodegen", date = "2020-01-07T08:47:08.031Z[GMT]")public class ApiOriginFilter implements javax.servlet.Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.addHeader("Access-Control-Allow-Origin", "*");
res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
res.addHeader("Access-Control-Allow-Headers", "Content-Type");
chain.doFilter(request, response);
System.out.println("Test");
}
public void destroy() {}
public void init(FilterConfig filterConfig) throws ServletException {}
}
I have an Ionic/React/Typescript application that I am building that performs the following API call the the Java API:
const apiClient = axios.create({
baseURL: 'http://10.0.29.1:8080/swagger-jaxrs-server.1.0.0/v1/',
responseType: 'json',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin' : '*'
}
});
const createUser = async (newUser: NewUser) => {
try {
const response = await apiClient.post<User>('/users', newUser);
const user = response.data;
return user;
} catch (err) {
if (err && err.response) {
const axiosError = err as AxiosError<ServerError>
if (axiosError.response)
{
return axiosError.response.data;
}
else
{
return 0;
}
}
It simply POSTS to "http://10.0.29.1:8080/swagger-jaxrs-server.1.0.0/v1/users". When this call is being performed in Chrome and Firefox I get the following response:
Chrome OPTIONS response:
Firefox OPTIONS response:
Both browsers of course complain about CORS (there is no Access-Control-Allow-Origin in the OPTIONS response headers):
Firefox CORS warning:
Chrome CORS warning
When I do the same thing in POSTMAN:
it works.
Going to /opt/tomcat/logs/catalina.out I get this:
The "Test" lines are from when I am POSTing from Postman. It is the System.out.println("Test"); code that I added in the ApiOriginFilter.java function. Means that the filter gets triggered when Postman sends OPTIONS. What is going on in the case of the browsers? Where is this response coming from and why is the filter not being triggered?
UPDATE: If I copy and paste the headers that Firefox/Chrome sents with the OPTIONS request to Postman, Postman still works. If I copy the Postman headers in a headers.txt file:
User-Agent: PostmanRuntime/7.21.0
Accept: */*
Cache-Control: no-cache
Postman-Token: b3b6ae69-58fe-44dc-bdf0-7280679f0f32
Host: 10.0.29.1:8080
Accept-Encoding: gzip, deflate
Content-Length: 0
Connection: keep-alive
and then perform curl -H #headers.txt -i -X OPTIONS http://10.0.29.1:8080/swagger-jaxrs-server.1.0.0/v1/users it responds
HTTP/1.1 200
Allow: OPTIONS, GET, HEAD, POST
Content-Length: 0
Date: Fri, 10 Jan 2020 06:47:45 GMT
Keep-Alive: timeout=20
Connection: keep-alive
which means the filter is not being called. If I simply curl -i -X OPTIONS http://10.0.29.1:8080/swagger-jaxrs-server.1.0.0/v1/users it responds
HTTP/1.1 200
Allow: OPTIONS, GET, HEAD, POST
Content-Length: 0
Date: Fri, 10 Jan 2020 06:51:37 GMT
still the filter is not being called.
This was one of those cases where a typo can make you wonder for hours. If you notice, the URL in Postman is http://10.0.29.1:8080/swagger-jaxrs-server-1.0.0/v1/ while in the source code it is http://10.0.29.1:8080/swagger-jaxrs-server.1.0.0/v1/. The . character between server and 1 is wrong, it should have been a -. I was also using the wrong address in cURL because I was copying it from the source code. At some point I even had two identical (to me) Postman OPTIONS requests, one working and one not working (!).
Here is how I brought myself to notice it:
First I added the RequestDumperFilter (Tomcat 7 or later) to the web.xml file of my webapp (located in /opt/tomcat/webapps/<your-webapp>/WEB-INF/web.xml, in order to capture all the HTTP headers (request/response), like this
<filter>
<filter-name>requestdumper</filter-name>
<filter-class>
org.apache.catalina.filters.RequestDumperFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>requestdumper</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
and in the /opt/tomcat/conf/logging.properties file I added a logging handler for the filter like this:
handlers = ..., 5request-dumper.org.apache.juli.FileHandler
...
5request-dumper.org.apache.juli.FileHandler.level = FINEST
5request-dumper.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
5request-dumper.org.apache.juli.FileHandler.prefix = request-dumper.
5request-dumper.org.apache.juli.FileHandler.formatter = org.apache.juli.VerbatimFormatter
org.apache.catalina.filters.RequestDumperFilter.level = INFO
org.apache.catalina.filters.RequestDumperFilter.handlers = 5request-dumper.org.apache.juli.FileHandler
credit: https://stackoverflow.com/a/8727615/1238675
However after sudo service tomcat restart the filter was not capturing the erroneous browser/curl requests at all. I then put the filter in /opt/tomcat/conf/web.xml so that it would capture any request made to Tomcat, not just my webapp. Indeed I started capturing the headers and I put side by side the logs caused by Postman and the logs caused by the browser:
It can be noticed that contextPath, pathInfo and servletPath are all wrong in the left side. This made me to immediately (finally) take a look at the URL and there you have it.

Strange "Allow" header in OPTIONS request to CORS-enabled spring boot endpoint

To test this, one can use the sample code from https://spring.io/guides/gs/rest-service-cors/ with no changes.
Here's the output from an OPTIONS request without any CORS headers:
$ curl -X OPTIONS -i http://localhost:8080/greeting HTTP/1.1 200
Allow: GET,HEAD,OPTIONS
Content-Length: 0
Date: Wed, 24 Jul 2019 16:45:25 GMT
As expected, the Allow header is correct, as the method is annotated with #GetMapping.
But now let's simulate a CORS preflight OPTIONS request (which is not really necessary for a GET, but that's not the point), adding Origin and Access-Control-Request-Method:
$ curl -X OPTIONS -H'Origin: http://localhost:9000' -H'Access-Control-Request-Method: GET' -i http://localhost:8080/greeting
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:9000
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 1800
Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
Content-Length: 0
Date: Wed, 24 Jul 2019 16:48:36 GMT
The CORS headers have been correctly included, but note that Allow now lists more methods than actually allowed (and which are indeed not allowed, with or without CORS; a 405 "Method not allowed" error is returned if one tries to POST to that URL).
Even more strange, Access-Control-Allow-Methods correctly lists only GET.
Am I misunderstanding some detail about how CORS should work, or is this a bug in Spring Boot?
Allow
The Allow header lists the set of methods support by a resource.
Access-Control-Allow-Methods
The Access-Control-Allow-Methods response header specifies the method or methods allowed when accessing the resource in response to a preflight request.
Allow just states what methods that are in general supported by the spring boot application. While Access-Control-Allow-Methods tells you what methods that you have access to.
As #Thomas stated allow is a Resource response header
So if you look closely at the #RequestMapping properties you will see method : RequestMethod[] https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#method--
If you go to RequestMethod docs you will find the following :
Java 5 enumeration of HTTP request methods. Intended for use with the
RequestMapping.method() attribute of the RequestMapping annotation.
Note that, by default, DispatcherServlet supports GET, HEAD, POST,
PUT, PATCH and DELETE only. DispatcherServlet will process TRACE and
OPTIONS with the default HttpServlet behavior unless explicitly told
to dispatch those request types as well: Check out the
"dispatchOptionsRequest" and "dispatchTraceRequest" properties,
switching them to "true" if necessary.
So by default #RequestMapping will allow [GET, HEAD, POST, PUT, PATCH , DELETE]
If you want to restrict some resource or method for specific methods you can use
#RequestMapping(method = {RequestMethod.GET,RequestMethod.POST})

How to handle Github Webhook in Java?

Simple question.Got registered Payload URL on Github: using ngrok.com (ngrok) link like explained in Github documentation for Webhooks: Creating Webhooks
ngrok definition: “I want to securely expose a local web server to the internet and capture all traffic for detailed inspection and replay.”
When i send POST request with payload from github on correct Payload URL the response code is 200, how can I handle that request/response and get payload (JSON) in java? With servlet or?
I have no idea where to start. Tried to search but nothing for Java :(
If i put ngrok.com/something, Eclipse console throw:[WARN] 404 - POST /pas (127.0.0.1) 1368 bytes
Request headers
Host: ....ngrok.com
X-Real-IP: 192....
X-Forwarded-Proto: http
Connection: close
Content-Length: 5759
Accept: */*
User-Agent: GitHub-Hookshot/e9dfd89
X-GitHub-Event: ping
X-GitHub-Delivery: c5493000-b67e-11e4-8199-8b09d3d66948
Content-Type: application/json
X-Hub-Signature: sha1=b2947ce6a6de23f4274831523bae375d64e20021
Response headers
Connection: close
Content-Type: text/html;charset=ISO-8859-1
Cache-Control: must-revalidate,no-cache,no-store
Content-Length: 1368
If i put good URL, status is 200. Response on Github Webhooks / Manage webhook:Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 1521
Content-Type: text/html
Date: Tue, 17 Feb 2015 10:17:46 GMT
Last-Modified: Thu, 12 Feb 2015 09:06:18 GMT
Server: nginx/1.6.2
So the question is actually "How to handle that payload?"
In documentation they use Sinatra and that's a big ? for me.
Sinatra code looks like this:
require "sinatra"
require "json"
post "/payload" do
push = JSON.parse(request.body.read)
puts "I got some JSON: #{push.inspect}"
end
New to this, sorry if its stupid question.
Resolved, i used HttpServlet doPost method to fetch request, then from request i getReader() and read line so i can make JSONObject. My servlet is on page/Payload and Webhook is on http://server.com/page/Payload
public class Payload extends HttpServlet {
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuilder builder = new StringBuilder();
String aux = "";
while ((aux = req.getReader().readLine()) != null) {
builder.append(aux);
}
String text = builder.toString();
try {
JSONObject json = new JSONObject(text);
String teams_url = json.getJSONObject("repository").getString("teams_url");
System.out.println("Teams URL:: "+teams_url);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}}
You can make use of gitlab4j-api library. For example usage, have a look at simple review project, exactly here: https://github.com/gitlab4j/simple-cr/blob/master/src/main/java/org/gitlab4j/simplecr/service/GitLabWebHookService.java

tomcat changes 404 status into 403 for http delete

I'm having a weird issue where if my servlet sends a 200 ok http status, it works as intended, and the client gets a 200 status as expected. But, if my servlet returns a 404 status, tomcat seems to change it into a 403 status. This doesn't happen if I use the http get method. I haven't tested put or post.
I want to make it very clear that my servlets doDelete method gets executed just fine. It's just that the status code returned to the browser gets changed.
I'll provide a minimal testcase demonstrating the issue.
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#WebServlet("/api/test403/*")
public class Test403 extends HttpServlet {
public void doDelete(HttpServletRequest request, HttpServletResponse response) {
try {
String p = request.getParameter("send404");
if (p != null && "1".equals(p)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "not found.");
} else {
response.sendError(HttpServletResponse.SC_OK, "ok.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static final long serialVersionUID = 1L;
}
then i test via the following urls
myproject/api/test403?send404=1
myproject/api/test403?send404=0
What could cause this behavior? I'm not overly familiar the whole servlet/container architecture. I'm only experiencing this issue on 1 server which uses tomcat 7.0.41. I tried another server, which didn't exhibit this behavior.
edit - Per request, here's some output from the network panel in chrome. I used ajax to initiate this particular request:
Request Headers
DELETE /xxxxx HTTP/1.1
Host: xxxxx
Connection: keep-alive
Origin: xxx
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Referer: xxx
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: xxx
Response Headers
HTTP/1.1 403 Forbidden
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Content-Encoding: gzip
Vary: Accept-Encoding
Date: Wed, 16 Apr 2014 02:30:32 GMT
I didn't remove any headers, although I anonymized some values.
A combination of...
an http DELETE request
calling HttpServletResponse.sendError(status, message) to send the 404 from your servlets doDelete() method
configuring a custom 404 error handler page(eg, via the <error-page> directive in web.xml)
Keeping the default value of readonly = true for your context
will cause the client to receive a 403 status instead of the 404 you though you sent.
A request to a servlet can service an http delete request without needing readonly to be false, but a request to a file cannot. What happens is that when you call sendError(), tomcat will try to find a custom error page that matches up with whatever http status you specified. In this case, it found one (/my404.html), and so in order to process it, it basically restarts the entire request routing/dispatching process, including running all the filters on the request. But, this time, since it's a static file request, it comes across a built in filter that looks for http the DELETE method, and then checks if readonly = false. If it's not, the request is rejected, and it changes the response status to 403 forbidden because you're not allowed to delete the static file named /my404.html.
A sensible workaround is to use HttpServletResponse.setStatus(status) instead of HttpServletResponse.sendError(status, message), so that tomcat doesn't try to find an error page. As mentioned by #BogdanZurac , you may also need to send a brief response body (i.e., "oops Error 404") in addition to setting the status to prevent it from seeking the custom error page.

http response is using chunked encoding when data is present

I have a web service that, when called, may or may not send a body back with the response. For some reason, whenever there is no data the Content-Length header is present, but when I send back a body, a Transfer-Encoding: Chunked header is present instead of the Content-Length header. The request being sent up is, in fact, chunked, but i don't necessarily need the response to be as we want the payload to be a small as possible.
As the following code illustrates, I have tried forcing the content length when data is sent back, but even so, the response still does not have a Content-Length header. I have read that the existence of a Transfer-Encoding: Chunked header will override any COntent-Length header, but I can't figure out how to remove the Transfer-Encoding header, or even why it is there in the first place.
Here is the my callback for a new request:
#Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setStatus(200);
String mac = req.getHeader("x-kcid");
String cmd = getCache(mac);
if (cmd != null) {
writeToStream(resp, cmd, "text/plain");
clearCache(mac);
}
}
and here is the method that actually writes the response:
private static void writeToStream(HttpServletResponse resp, String msg, String contentType) throws IOException {
resp.setContentType(contentType);
resp.setContentLength(msg.getBytes().length);
resp.getWriter().write(msg);
}
GAE doesn't allow setting the Transfer-Encoding or Content-Length header.
(That headers, and some others are ignored and removed from the response).

Categories