is it possible to change response on proxy level using varnish? - nginx

For example we have setup like this:
user -> api gateway -> (specific endpoint) varnish -> backend service
If backend returns response 500 {"message":"error"} I want to patch this response and return 200 "[]" instead.
Is it possible to do something like this using varnish or some other proxy?

It is definitely possible to intercept backend errors, and convert them into regular responses.
A very simplistic example is the following:
sub vcl_backend_error {
set beresp.http.Content-Type = "application/json";
set beresp.status = 200;
set beresp.body = "[]";
return(deliver);
}
sub vcl_backend_response {
if(beresp.status == 500) {
return(error(200,"OK"));
}
}
Whenever your backend would fail, and return an HTTP/503 error, we will send a HTTP/200 response with [] output.
This output template for backend errors is also triggered when the backend does reply, but with a HTTP/500 error.
In real world scenarios, I would a some conditional logic in vcl_backend_error to only return the JSON output template when specific criteria are matched. For example: a certain URL pattern was matched.
I would advise the same in vcl_backend_response: maybe you don't want to convert all HTTP/500 errors into regular HTTP/200 responses. Maybe you also want to add conditional logic.

Related

'Access-Control-Allow-Origin' missing using actix-web

Stuck on this problem where I received this error everytime making POST request to my actix-web server.
CORS header 'Access-Control-Allow-Origin' missing
my javascript (VueJs running on localhost:3000) :
let data = //some json data
let xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8080/abc");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = () => {
console.log(xhr.responseText);
}
xhr.send(JSON.stringify(data));
My Actix_Web server (running on localhost:8080) :
#[actix_web::main]
async fn main() {
HttpServer::new(move || {
let cors = Cors::default()
.allowed_origin("http://localhost:3000/")
.allowed_methods(vec!["GET", "POST"])
.allowed_header(actix_web::http::header::ACCEPT)
.allowed_header(actix_web::http::header::CONTENT_TYPE)
.max_age(3600);
App::new()
.wrap(cors)
.service(myfunc)
})
.bind(("0.0.0.0", 8080))
.unwrap()
.run()
.await
.unwrap();
}
my cargo.toml dependencies
[dependencies]
actix-web = "4"
actix-cors = "0.6.1"
...
Got any idea?
Okay, so I've done some testing. If you're writing a public API, you probably want to allow all origins. For that you may use the following code:
HttpServer::new(|| {
let cors = Cors::default().allow_any_origin().send_wildcard();
App::new().wrap(cors).service(greet)
})
If you're not writing a public API... well, I'm not sure what they want you to do. I've not figured out how to tell the library to send that header. I guess I will look at the code.
UPDATE:
So funny story, this is how you allow specific origins:
let cors = Cors::default()
.allowed_origin("localhost:3000")
.allowed_origin("localhost:2020");
BUT, and oh boy, is that but juicy. The Access-Control-Allow-Origin response header is only set when there is a Origin request header. That header is normally added by the browser in certain cases 1. So I did that (using the Developer tools in the browser). What did I get? "Origin is not allowed to make this request". I set my origin header to localhost:3000. Turns out, the arctix library simply discards that header if no protocol was provided... (e.g. http://) (I assume it discards it, if it deems its format invalid). That internally results in the header being the string "null". Which is, checks notes, not in the list of allowed origins.
And now the grand finale:
Your origin header needs to be set to (by either you or the browser): "http://localhost:3000".
Your configuration needs to include: .allowed_origin("http://localhost:3000").
After doing that, the server will happily echo back your origin header in the Access-Control-Allow-Origin header. And it will only send that one.
I've no idea if any of that is what the standard specifies (or not). I encourage you to read through it, and if it doesn't comply, please open an issue on GitHub. I would do it myself, but I'm done with programming for today.
Cheers!

play framework server-side redirect on unauthorized async request

I am using Play Framework / Reactjs in a single page application.
The authentication is built using ActionBuilders, such that any async request made from the browser is protected by the "AuthorizedAction".
Here is the implementation of the ActionBuiler's invokeBlock:
override def invokeBlock[A](request: Request[A], block: (AuthorizedRequest[A]) ⇒ Future[Result]): Future[Result] = {
val maybeToken = request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey))
maybeToken match {
case Some(token) => cache.get[Long](token) match {
case Some(u_id) =>
val user = User.findByID(u_id).get
val req = AuthorizedRequest(request, user)
block(req)
case None => Future.successful(Unauthorized) // REDIRECT HERE
}
case None => Future.successful(Unauthorized) // REDIRECT HERE
}
}
This is working great, with one exception: I would like instead of simply returning Unauthorized to also redirect to the login page. Because as of now, if the token times out, and the user causes an async request to be fired, it will simply error out in the console, and the page will look broken.
method 1:
replace the two Future.successful lines above with:
Future.successful(Redirect("/login"))
this actually does call the controller for the /login route, however the URL does not change. Instead, the response of the async request that was fired contains the data =
`<!DOCTYPE html ...
Here's an example of async request whose response.data contains the screenshot text below:
axios('/ping')
.then((response) => {
let userID = response.data; // contains junk
store.dispatch(SomeAction.xyz(userID))
})
.catch((error) => {
browserHistory.push('/login');
})
Looks like I'm missing something crucial here. Any ideas how to proceed from here on?
method 2:
on the front-end, use React router's browserHistory.push('/login')
in all the async requests' .catch()
Though I would like to avoid this as much as possible, if method 1 can successfully work.
Thanks
Turns out this has less to do with Play Framework and more with HTTP codes/responses and how they are interpreted by the browser.
It seems we should not return Redirects to async requests, as they would cause the issue of method 1 in the question.
Instead, people have been suggesting one of the following:
return Ok/200 with some specific header indicating to the front-end that a redirect must take place
return some custom error code, e.g. Status(xyz) that the front-end code would understand and interpret as a request to redirect to some route.
Choosing the appropriate HTTP code is another topic, and from the two options above, we have three choices:
200 or custom 2xx code, interpreted as a success and handled as such in the promise
3xx custom code, may seem appropriate as we are asking for a redirect
401 or custom 4xx code. Technically, the incoming async request was Unauthorized (401), and so should be an error level response.
The crucial part is that handling the response code seems like it will have to be at the front-end, along the lines of method 2 in the question.
I'm leaning towards the default 401

Arbitrary response content types in Feathers

I have a custom service that must return data in CSV format.
I can't use a standard Express route, because I need Feathers' hooks on this endpoint.
I couldn't find an example of a Feathers service returning non-HTML, non-JSON data, and have found no way to specify a response content type.
Using res.set('Content-Type', 'text/csv') before returning from the service method didn't work; the final Content-Type header was reset to application/json, even though the method's return value was a regular string.
How can I properly set arbitrary response content types in Feathers' custom service methods?
You can customize the response format like this:
const feathers = require('feathers');
const rest = require('feathers-rest');
const app = feathers();
function restFormatter(req, res) {
res.format({
'text/plain': function() {
res.end(`The Message is: "${res.data.text}"`);
}
});
}
app.configure(rest(restFormatter));
The complete documentation can be found here.
Using your own service specific middleware to send the response should also work.

How to pass new header to sendRedirect

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

Need some examples of dispatch-nio to make several Http requests

I m using dispatch-nio library to make several Http request calls. But I cannot find an example how to handle callback responses through nio library.
And also I am unable to handle exceptions thrown by nio.Http call. Could anyone please post an example or an url where we can find some information of it.
So far I achieved:
val http = new nio.Http
withShutdown(http) {
val host = :/("http://example.org") /"api"
val withParams = host <<? Map(
"page" -> "1"
)
val future = http(withParams as_str)
while (!future.isSet) {
println("Waiting for results...")
Thread.sleep(15000)
}
future.apply()
}
}
How to set headers and onComplete? any example please
Thanks in advance.

Resources