create stand error page for zuul [version 1.1.2] - netflix-zuul

I am trying to create standard error page for zuul server so that I can redirect exception to this page?
Currently, I have created a zuul filter to catch zuul exception as below:
code snippets
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
Object e = ctx.get("error.exception");
if (e != null && e instanceof ZuulException) {
ZuulException zuulException = (ZuulException)e;
LOG.error("Zuul failure detected: " + zuulException.getMessage(), zuulException);
// Remove error code to prevent further error handling in follow up filters
ctx.remove("error.status_code");
// Populate context with new response values
ctx.setResponseBody("Internal Server Error : Please contact Phoenix Admin");
ctx.getResponse().setContentType("application/json");
ctx.setResponseStatusCode(500); //Can set any error code as excepted
}
}
catch (Exception ex) {
LOG.error("Exception filtering in custom error filter", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
appreciate for any advice?

I will have to spend some time to see how you can read from the error page I am traveling right now. But setting the response body by reading content might not be a bad option definitely might not be perfect.
Checkout the below code if it helps.
Also add some code that you might be trying and is not working its easier that way to answer and help.
You need to make sure the filter runs after the predecoration filter.
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String url = UriComponentsBuilder.fromHttpUrl("http://localhost:8082").path("/outage").build()
.toUriString();
ctx.set("requestURI", url);
return null;
}
Check this out for more information:- https://github.com/spring-cloud/spring-cloud-netflix/issues/1754
Hope this helps you.

Related

Spring Security Webflux/Reactive Exception Handling

I'm building app on spring webflux, and i'm stuck because spring security webflux (v.M5) did not behave like Spring 4 in term of exception handling.
I saw following post about how to customise spring security webflux:
Spring webflux custom authentication for API
If we throw exception let say in ServerSecurityContextRepository.load, Spring will update http header to 500 and nothing i can do to manipulate this exception.
However, any error thrown in controller can be handled using regular #ControllerAdvice, it just spring webflux security.
Is there anyway to handle exception in spring webflux security?
The solution I found is creating a component implementing ErrorWebExceptionHandler. The instances of ErrorWebExceptionHandler bean run before Spring Security filters. Here's a sample that I use:
#Slf4j
#Component
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
#Autowired
private DataBufferWriter bufferWriter;
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
AppError appError = ErrorCode.GENERIC.toAppError();
if (ex instanceof AppException) {
AppException ae = (AppException) ex;
status = ae.getStatusCode();
appError = new AppError(ae.getCode(), ae.getText());
log.debug(appError.toString());
} else {
log.error(ex.getMessage(), ex);
}
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
exchange.getResponse().setStatusCode(status);
return bufferWriter.write(exchange.getResponse(), appError);
}
}
If you're injecting the HttpHandler instead, then it's a bit different but the idea is the same.
UPDATE: For completeness, here's my DataBufferWriter object, which is a #Component:
#Component
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
#Slf4j
public class DataBufferWriter {
private final ObjectMapper objectMapper;
public <T> Mono<Void> write(ServerHttpResponse httpResponse, T object) {
return httpResponse
.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = httpResponse.bufferFactory();
try {
return bufferFactory.wrap(objectMapper.writeValueAsBytes(object));
} catch (Exception ex) {
log.warn("Error writing response", ex);
return bufferFactory.wrap(new byte[0]);
}
}));
}
}
There is no need to register any bean and change default Spring behavior. Try more elegant solution instead:
We have:
The custom implementation of the ServerSecurityContextRepository
The method .load return Mono
public class HttpRequestHeaderSecurityContextRepository implements ServerSecurityContextRepository {
....
#Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
List<String> tokens = exchange.getRequest().getHeaders().get("X-Auth-Token");
String token = (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null;
Mono<Authentication> authMono = reactiveAuthenticationManager
.authenticate( new HttpRequestHeaderToken(token) );
return authMono
.map( auth -> (SecurityContext)new SecurityContextImpl(auth))
}
}
The problem is: if the authMono will contains an error instead of Authentication - spring will return the http response with 500 status (which means "an unknown internal error") instead of 401. Even the error is AuthenticationException or it's subclass - it makes no sense - Spring will return 500.
But it is clear for us: an AuthenticationException should produce the 401 error...
To solve the problem we have to help Spring how to convert an Exception into the HTTP response status code.
To make it we have can just use the appropriate Exception class: ResponseStatusException or just map an original exception to this one (for instance, by adding the onErrorMap() to the authMono object). See the final code:
public class HttpRequestHeaderSecurityContextRepository implements ServerSecurityContextRepository {
....
#Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
List<String> tokens = exchange.getRequest().getHeaders().get("X-Auth-Token");
String token = (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null;
Mono<Authentication> authMono = reactiveAuthenticationManager
.authenticate( new HttpRequestHeaderToken(token) );
return authMono
.map( auth -> (SecurityContext)new SecurityContextImpl(auth))
.onErrorMap(
er -> er instanceof AuthenticationException,
autEx -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, autEx.getMessage(), autEx)
)
;
)
}
}
I just went trough lots of documentation, having a similar problem.
My solution was using ResponseStatusException. AccessException of Spring-security seems to be understood.
.doOnError(
t -> AccessDeniedException.class.isAssignableFrom(t.getClass()),
t -> AUDIT.error("Error {} {}, tried to access {}", t.getMessage(), principal, exchange.getRequest().getURI())) // if an error happens in the stream, show its message
.onErrorMap(
SomeOtherException.class,
t -> { return new ResponseStatusException(HttpStatus.NOT_FOUND, "Collection not found");})
;
If this goes in the right direction for you, I can provide a bit better sample.

MVC Post Request how to get Request Content if it's different to that expected by the Default ModelBinder

I have this MVC WebApi action:
PostTrips(List<Trip> trips)
When a list of trips is sent through everything works fine. If, however, someone is trying to post incorrect data, e.g just an object {} then trips is null - this is fine, but I would like to log the data that the user tried to push.
I tried to get it using string requestData = Request.Content.ReadAsStringAsync().Result; but it can only be called once, and I guess the default model binder is calling it to try an map it to my List<Trip>, as when I call it, the result is always null, even though I know I'm passing something in.
Does anyone know of another way to get the posted data again?
I got around this my removing the parameter List<Trip> trips from the action so I had:
public async Task<HttpResponseMessage> PostTrips()
{
}
This bypasses the default model binder and allows you to get the unmodified request content using:
string requestContent = await Request.Content.ReadAsStringAsync();
You can then do what ever you need with this - I wanted to log the data for error tracking.
To create the actual List<Trip> trips I then used Newtonsoft.Json to deserialise the string into a list:
List<TravelTrackerTrip> appTrips = JsonConvert.DeserializeObject<List<TravelTrackerTrip>>(requestContent);
Full example:
public async Task<HttpResponseMessage> PostTrips()
{
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
List<Trip> appTrips = null;
string requestContent = await Request.Content.ReadAsStringAsync();
try
{
appTrips = JsonConvert.DeserializeObject<List<Trip>>(requestContent);
}
catch(Exception ex)
{
//ERROR LOGGING HERE...
//QUIT - Return failure response
}
try
{
//Success - do whatever we need
}
catch(Exception ex)
{
//ERROR LOGGING HERE...
//QUIT - Return failure response
}
//Return success response
}

Ruby on Rails default to POST when accessed with Android Studio

I have some experience using android studio but am fairly new to ruby on rails and have been trying to create a restful application. I generated a scaffold called Employer so it has the default routes,
employers GET /employers(.:format) employers#index
POST /employers(.:format) employers#create
and have been trying to read the employers list by parsing the JSON like so in android studio:
ProgressDialog pdLoading = new ProgressDialog(MainActivity.this);
HttpURLConnection conn;
URL url = null;
String response = "";
TextView errdisplay = (TextView)findViewById(R.id.fishytext);
#Override
protected void onPreExecute() {
super.onPreExecute();
//this method will be running on UI thread
pdLoading.setMessage("\tLoading...");
pdLoading.setCancelable(false);
pdLoading.show();
}
#Override
protected String doInBackground(String... params) {
try {
// Enter URL address where your json file resides
url = new URL("MYIP.../employers.json");
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return e.toString();
}
try {
// Setup HttpURLConnection class to send and receive data from php and mysql
conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(READ_TIMEOUT);
conn.setConnectTimeout(CONNECTION_TIMEOUT);
conn.setRequestMethod("GET");
// setDoOutput to true as we recieve data from json file
conn.setDoOutput(true);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return e1.toString();
}
try {
int response_code = conn.getResponseCode();
// Check if successful connection made
if (response_code == HttpURLConnection.HTTP_OK) {
// Read data sent from server
InputStream input = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
response+= line;
}
return (result.toString());
} else {
return ("unsuccessful");
}
} catch (IOException e) {
e.printStackTrace();
return e.toString();
} finally {
conn.disconnect();
}
}
My problem is that despite changing my RequestMethod to GET it defaults to POST in rails which has left me quite confused.
Started POST "/employers.json"
I understand this is a routes issue and have managed to get it work(if you can call it that) through forcibly changing the routes as shown below but i realize this will affect my future attempts at REST.
post 'employers' => 'employers#index'
I apologize if this isn't the right format but i would be very grateful for any advice on how to fix my routes or how to access the JSON url. Thank you for your time.
Many thanks to the user sahil for pointing me in the right direction. At the moment i still haven't managed to access Rails though anything other than POST so for anybody in a similar situation the work around im using is to change the routes to modify the url.
post 'employers/display' => 'employers#index'
post 'employers/newindex' => 'employers#create'
so that the url i need to access instead will be "/employers/display.json" for GET and "/employers/newindex.json" for POST, from how i defined them in the employers_controller. It's not a very good answer but ill update this again when i know more.

Making http requests from within a ApiController doesn't work

Here is a simplified version of the API controller I created.
public class SampleController : ApiController
{
[System.Web.Http.HttpGet]
public string Test(string url)
{
try
{
using (WebClient webClient = new WebClientEx())
{
return webClient.DownloadString(url);
}
}
catch (Exception ex)
{
return string.Empty;
}
}
Given a perfectly valid url this will throw a WebException that says "The remote name could not be resolved".
If I execute the same method within LinqPad, using the same url, it works. I've tried WebClient and WebRequest with the same results.
To be clear, this isn't a routing issue as I am able to hit and step through the code either way and the url involved is not part of this application.
I figured it out. I was initially thrown off because it appeared to work in a regular Controller but not in an ApiController. After I failed to reproduce my earlier "success", I finally figured out that I needed to use the system proxy.
webClient.Proxy = WebRequest.GetSystemWebProxy();

Error Handling in ASP.NET MVC

How can I correctly handle exceptions thrown from controllers in ASP.NET MVC? The HandleError attribute seems to only process exceptions thrown by the MVC infrastructure and not exceptions thrown by my own code.
Using this web.config
<customErrors mode="On">
<error statusCode="401" redirect="/Errors/Http401" />
</customErrors>
with the following code
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
// Force a 401 exception for testing
throw new HttpException(401, "Unauthorized");
}
}
}
doesn't result in what I was hoping for. Instead I get the generic ASP.NET error page telling me to modify my web.config to see the actual error information. However, if instead of throwing an exception I return an invalid View, I get the /Shared/Views/Error.aspx page:
return View("DoesNotExist");
Throwing exceptions within a controller like I've done above seems to bypass all of the HandleError functionality, so what's the right way to create error pages and how do I play nice with the MVC infrastructure?
Controller.OnException(ExceptionContext context). Override it.
protected override void OnException(ExceptionContext filterContext)
{
// Bail if we can't do anything; app will crash.
if (filterContext == null)
return;
// since we're handling this, log to elmah
var ex = filterContext.Exception ?? new Exception("No further information exists.");
LogException(ex);
filterContext.ExceptionHandled = true;
var data = new ErrorPresentation
{
ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
TheException = ex,
ShowMessage = !(filterContext.Exception == null),
ShowLink = false
};
filterContext.Result = View("ErrorPage", data);
}
Thanks to kazimanzurrashaid, here is what I wound up doing in Global.asax.cs:
protected void Application_Error()
{
Exception unhandledException = Server.GetLastError();
HttpException httpException = unhandledException as HttpException;
if (httpException == null)
{
Exception innerException = unhandledException.InnerException;
httpException = innerException as HttpException;
}
if (httpException != null)
{
int httpCode = httpException.GetHttpCode();
switch (httpCode)
{
case (int) HttpStatusCode.Unauthorized:
Response.Redirect("/Http/Error401");
break;
}
}
}
I'll be able to add more pages to the HttpContoller based on any additional HTTP error codes I need to support.
The HandleError attribute seems to only process exceptions thrown by the MVC infrastructure and not exceptions thrown by my own code.
That is just wrong. Indeed, HandleError will only "process" exceptions either thrown in your own code or in code called by your own code. In other words, only exceptions where your action is in the call stack.
The real explanation for the behavior you're seeing is the specific exception you're throwing. HandleError behaves differently with an HttpException. From the source code:
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500) {
return;
}
I don't think you will be able to show specific ErrorPage based upon the HttpCode with the HandleError Attribute and I would prefer to use an HttpModule for this purpose. Assuming that I have folder "ErrorPages" where different page exists for each specific error and the mapping is specifed in the web.config same as the regular web form application. And the following is the code which is used to show the error page:
public class ErrorHandler : BaseHttpModule{
public override void OnError(HttpContextBase context)
{
Exception e = context.Server.GetLastError().GetBaseException();
HttpException httpException = e as HttpException;
int statusCode = (int) HttpStatusCode.InternalServerError;
// Skip Page Not Found and Service not unavailable from logging
if (httpException != null)
{
statusCode = httpException.GetHttpCode();
if ((statusCode != (int) HttpStatusCode.NotFound) && (statusCode != (int) HttpStatusCode.ServiceUnavailable))
{
Log.Exception(e);
}
}
string redirectUrl = null;
if (context.IsCustomErrorEnabled)
{
CustomErrorsSection section = IoC.Resolve<IConfigurationManager>().GetSection<CustomErrorsSection>("system.web/customErrors");
if (section != null)
{
redirectUrl = section.DefaultRedirect;
if (httpException != null)
{
if (section.Errors.Count > 0)
{
CustomError item = section.Errors[statusCode.ToString(Constants.CurrentCulture)];
if (item != null)
{
redirectUrl = item.Redirect;
}
}
}
}
}
context.Response.Clear();
context.Response.StatusCode = statusCode;
context.Response.TrySkipIisCustomErrors = true;
context.ClearError();
if (!string.IsNullOrEmpty(redirectUrl))
{
context.Server.Transfer(redirectUrl);
}
}
}
One other possibility (not true in your case) that others reading this may be experiencing is that your error page is throwing an error itself, or is not implementing :
System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>
If this is the case then you will get the default error page (otherwise you'd get an infinite loop because it would keep trying to send itself to your custom error page). This wasn't immediately obvious to me.
This model is the model sent to the error page. If your error page uses the same master page as the rest of your site and requires any other model information then you will need to either create your own [HandleError] type of attribute or override OnException or something.
protected override void OnException (ExceptionContext filterContext )
{
if (filterContext != null && filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
this.View("Error").ViewData["Exception"] = filterContext.Exception.Message;
this.View("Error").ExecuteResult(this.ControllerContext);
}
}
I chose the Controller.OnException() approach, which to me is the logical choice - since I've chosen ASP.NET MVC, I prefer to stay at the framework-level, and avoid messing with the underlying mechanics, if possible.
I ran into the following problem:
If the exception occurs within the view, the partial output from that view will appear on screen, together with the error-message.
I fixed this by clearing the response, before setting filterContext.Result - like this:
filterContext.HttpContext.Response.Clear(); // gets rid of any garbage
filterContext.Result = View("ErrorPage", data);
Jeff Atwood's User Friendly Exception Handling module works great for MVC. You can configure it entirely in your web.config, with no MVC project source code changes at all. However, it needs a small modification to return the original HTTP status rather than a 200 status. See this related forum post.
Basically, in Handler.vb, you can add something like:
' In the header...
Private _exHttpEx As HttpException = Nothing
' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
_exHttpEx = CType(ex, HttpException)
HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If

Resources