How to reflect the incoming GET request in text? - java

I'm using Spring MVC and Springboot (assume latest version) to put up a simple web service that returns some text.
I was able to figure out how to use #RequestMapping and #PathVariable annotations to show one of the URL path variables as a response from the server (e.g. if the user goes to .../my_user_id/ in the browser, they can then see some text in the browser that includes that user_id... since the service returned it as a response).
I need help with figuring out how to capture the GET HTTP request the user's browser makes and then display it in text form as a response in the browser (I want to display the headers and the request body as plain text).
I've done some research, but none of the solutions available work properly. Is anyone aware of the right approach / feasibility here?
An approach I tried:
http://www.mkyong.com/java/how-to-get-http-request-header-in-java/
Some threads on the error I get back when I tried the above approach:
Spring RestTemplate - how to enable full debugging/logging of requests/responses?
http://forum.spring.io/forum/spring-projects/roo/84244-circular-view-path-resourcenotfound
Circular view path
How to avoid the "Circular view path" exception with Spring MVC test
http://myshittycode.com/2014/01/17/mockmvc-circular-view-path-view-would-dispatch-back-to-the-current-handler-url-view-again/
More on Spring MVC, which I'm heavily using:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html
Anyhow. When using the below #Controller, I get an error:
#RestController
public class HelloController {
#RequestMapping("/")
public String index() {
return "Welcome to your home directory";
}
#RequestMapping(value="/mydata/{userId}", method=RequestMethod.GET)
public String printTheUser(#PathVariable String userId) {
return "The data for " + userId + " would live here";
}
#RequestMapping("/summary_data/")
public String index3() {
return "All summary data would appear here";
}
private String server = "localhost";
private int port = 8080;
#RequestMapping("/request_mirror/**")
public #ResponseBody String mirrorRest(#RequestBody String body, HttpMethod method, HttpServletRequest request,
HttpServletResponse response) throws URISyntaxException {
URI uri = new URI("http", null, server, port, request.getRequestURI(), request.getQueryString(), null);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity =
restTemplate.exchange(uri, method, new HttpEntity<String>(body), String.class);
return responseEntity.getBody();
}
}
When running this code and navigating to localhost:8080/request_mirror/stuff3/, I get the following error:
Whitelabel Error Page. This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Feb 08 15:41:13 EST 2016
There was an unexpected error (type=Bad Request, status=400).
Required request body content is missing: org.springframework.web.method.HandlerMethod$HandlerMethodParameter#a35a9b3f
Now, when I try a different approach (another #Controller) - the code looks like:
#Controller
#RequestMapping("/site")
public class SecondController{
#Autowired
private HttpServletRequest request;
#RequestMapping(value = "/{input:.+}", method = RequestMethod.GET)
public ModelAndView getDomain(#PathVariable("input") String input) {
ModelAndView modelandView = new ModelAndView("result");
modelandView.addObject("user-agent", getUserAgent());
modelandView.addObject("headers", getHeadersInfo());
return modelandView;
}
//get user agent
private String getUserAgent() {
return request.getHeader("user-agent");
}
//get request headers
private Map<String, String> getHeadersInfo() {
Map<String, String> map = new HashMap<String, String>();
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
}
For the above code (SecondController), (sourced from http://www.mkyong.com/java/how-to-get-http-request-header-in-java/), I get the following error, when I try to navigate to localhost:8080/site/stuff123456789... (but I can see the header keys and values from the request in the Map upon inspection... just not sure how to display them as text in the browser as the response).
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Feb 08 16:10:47 EST 2016
There was an unexpected error (type=Internal Server Error, status=500).
Circular view path [stuff123456789]: would dispatch back to the current handler URL [/site/stuff123456789] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

EDIT: Use the HttpEntity to get the body in case it's empty.
I'm not sure exactly what you're trying to achieve, but think this might be close:
#RequestMapping(value="/echoRequest")
public #ResponseBody String echoRequest(HttpEntity<String> httpEntity, HttpServletRequest req) {
String out = "";
List<String> names = req.getHeaderNames();
for (String name : names) {
out += (name + ": " + req.getHeader(name) + "\n");
}
if (httpEntity.hasBody()) {
out += httpEntity.getBody();
}
return out;
}

Related

I can not take a value from the header in the Controller in Spring Boot

I have built a web application in Spring Boot that has a form and besides the values from the form that it inserts in the database it takes from the header the username. This is the code from the Controller:
#PostMapping("/BilantErr/")
public String postIndex(#RequestParam(name = "cui") String cui, #RequestParam(name = "an_raportare") String anRaportare,
#RequestParam(name = "perioada") String perioada,
#RequestHeader("iv-user") String operator, #RequestParam(name = "motivatie") String motivatie, Model model) {
String page = "";
//String operator = "serban";
try {
if (repository.insert(cui, anRaportare, operator, motivatie, perioada) == true) {
page = "success";
} else {
page = "error";
}
} catch (Exception e) {
Logger logger = LoggerFactory.getLogger(BilantErr.class);
}
return page;
}
The error that I am getting is : Resolved [org.springframework.web.bind.MissingRequestHeaderException: Required request header 'iv-user' for method parameter type String is not present]
What may be the problem ? There is an application already built in JSF that works and takes the user from the header and I have to replace it with a Spring app. What am I doing wrong ? Thanks
MissingRequestHeaderException means the HTTP request doesn't contain a "iv-user" header. You must have a look to your request first. You can read all headers of the HTTP request by following code snippet:
#GetMapping("/listHeaders")
public ResponseEntity<String> multiValue(
#RequestHeader MultiValueMap<String, String> headers) {
headers.forEach((key, value) -> {
LOG.info(String.format(
"Header '%s' = %s", key, value.stream().collect(Collectors.joining("|"))));
});
return new ResponseEntity<String>(
String.format("Listed %d headers", headers.size()), HttpStatus.OK);
}
The request header "iv-user" is required but does not seem to be present in the request you receive. You could fix your request or make the header optional: #RequestHeader(value = "iv-user", required = false)

Redirect from Spring REST controller with parameters

I have two different Spring applications (app-one, app-two), app-one should receive a response and then redirect to app-two with some parameters. So, I have the following REST controller in app-one:
#RestController
public class RedirectController
{
#GetMapping(value = "/redirect")
public ResponseEntity<Void> redirectEndpoint(HttpServletRequest request,
RedirectAttributes redirectAttributes)
{
// Do some business logic
// Set parameters
redirectAttributes.addAttribute("attribute", "Value 1");
redirectAttributes.addFlashAttribute("flashAttribute", "Value 2");
// Redirect to success URL
String redirectURL = "http://app-two/success";
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(redirectURL));
return ResponseEntity.status(HttpStatus.FOUND).headers(headers).build();
}
}
And the following REST controller in app-two:
#RestController
public class SuccessController
{
#GetMapping(value = "/success")
public ResponseEntity<Void> successEndpoint(HttpServletRequest request, Model model,
#RequestParam(value = "attribute", required = false) String attribute,
#ModelAttribute(value = "flashAttribute") String flashAttribute)
{
// Get parameters
System.out.println("attribute: " + attribute);
System.out.println("flashAttribute: " + flashAttribute);
String flashAttributeFromModelMap = (String) model.asMap().get("flashAttribute");
System.out.println("flashAttributeFromModelMap: " + flashAttributeFromModelMap);
Map<String, ?> flashMap = RequestContextUtils.getInputFlashMap(request);
if (flashMap != null)
{
String flashAttributeFromFlashMap = (String) flashMap.get("flashAttribute");
System.out.println("flashAttributeFromFlashMap: " + flashAttributeFromFlashMap);
}
// Do some business logic
return ResponseEntity.status(HttpStatus.OK).build();
}
}
I was able to redirect successfully by returning FOUND (302). But when adding attributes to RedirectAttributes (in this case attribute and flashAttribute), these attributes are not found after the redirection done (attribute gets null and flashAttribute gets empty).
I tried to get the attributes values by different ways (#RequestParam, #ModelAttribute, model.asMap().get(), and RequestContextUtils.getInputFlashMap(request).get()) but none of them gets the correct value.
What I need is to get the correct attributes' values in successEndpoint. Any suggestions on how to accomplish that?
Thanks in advance.

springtoolsuite rest service not referencing function

I am new to java and trying to implement a rest web service with spring tool suite. I successfully ran an example from a guide and tried to add a POST function to the basic Hello World service. The web service is running using the Spring boot App and all I can trace is that the function is not found. 404 status. Here is code:
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
private static final Logger logger = LoggerFactory.getLogger(RestController.class);
#RequestMapping(value = "/greeting", method = RequestMethod.GET)
public #ResponseBody Greeting greeting(#RequestParam(value="name", defaultValue="World") String name, HttpServletResponse httpResponse_p,
WebRequest request_p) {
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
// #Secured({ "ROLE_USER" })
#RequestMapping(method=RequestMethod.POST, value= {"/addNewPage/{customername}/{streamname}/{name}"})
public Greeting addName(#RequestBody String body, #PathVariable("customername") String customername, #PathVariable("streamname") String streamname,
#PathVariable("name") String name, HttpServletResponse httpResponse_p, WebRequest request_p) {
if (customername.isEmpty() || streamname.isEmpty()) {
String eMessage = "ERROR - NO PARAMETERS INCLUDED!";
httpResponse_p.setStatus(HttpStatus.BAD_REQUEST.value());
return new Greeting (counter.incrementAndGet(), String.format(template, "BAD PARAMETERS"));
}
return new Greeting(counter.incrementAndGet(), String.format("WORKING - ADDED " + name));
}
So if I paste the following in my browser:
http://localhost:8080/greeting?name=Al
I get the following correct response:
{"id":2,"content":"Hello, Al!"}
But if I try
http://localhost:8080/addNewPage/something/stream1/ABC
I get the following:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing
this as a fallback.
Tue Mar 24 17:19:29 EDT 2015
There was an unexpected error (type=Not Found, status=404).
No message available
could someone see what I am missing here? Or be so kind to suggest a good step by step tutorial that goes through the following functions GET/POST/PUT/DELETE?
When you paste the url in the browser you are doing a GET. Your mapping is for POST so a 404 error is what expected.
Normally when you POSTing you should have some data in the request body but anyway just for testing you can use curl to send post requests.
Here is a tutorial on how to use it for testing rest apis

How to handle exceptions in Spring MVC differently for HTML and JSON requests

I'm using the following exception handler in Spring 4.0.3 to intercept exceptions and display a custom error page to the user:
#ControllerAdvice
public class ExceptionHandlerController
{
#ExceptionHandler(value = Exception.class)
public ModelAndView handleError(HttpServletRequest request, Exception e)
{
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
}
But now I want a different handling for JSON requests so I get JSON error responses for this kind of requests when an exception occurred. Currently the above code is also triggered by JSON requests (Using an Accept: application/json header) and the JavaScript client doesn't like the HTML response.
How can I handle exceptions differently for HTML and JSON requests?
The ControllerAdvice annotation has an element/attribute called basePackage which can be set to determine which packages it should scan for Controllers and apply the advices. So, what you can do is to separate those Controllers handling normal requests and those handling AJAX requests into different packages then write 2 Exception Handling Controllers with appropriate ControllerAdvice annotations. For example:
#ControllerAdvice("com.acme.webapp.ajaxcontrollers")
public class AjaxExceptionHandlingController {
...
#ControllerAdvice("com.acme.webapp.controllers")
public class ExceptionHandlingController {
The best way to do this (especially in servlet 3) is to register an error page with the container, and use that to call a Spring #Controller. That way you get to handle different response types in a standard Spring MVC way (e.g. using #RequestMapping with produces=... for your machine clients).
I see from your other question that you are using Spring Boot. If you upgrade to a snapshot (1.1 or better in other words) you get this behaviour out of the box (see BasicErrorController). If you want to override it you just need to map the /error path to your own #Controller.
As you have the HttpServletRequest, you should be able to get the request "Accept" header. Then you could process the exception based on it.
Something like:
String header = request.getHeader("Accept");
if(header != null && header.equals("application/json")) {
// Process JSON exception
} else {
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
Since i didn't find any solution for this, i wrote some code that manually checks the accept header of the request to determine the format. I then check if the user is logged in and either send the complete stacktrace if he is or a short error message.
I use ResponseEntity to be able to return both JSON or HTML like here.
Code:
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleExceptions(Exception ex, HttpServletRequest request) throws Exception {
final HttpHeaders headers = new HttpHeaders();
Object answer; // String if HTML, any object if JSON
if(jsonHasPriority(request.getHeader("accept"))) {
logger.info("Returning exception to client as json object");
headers.setContentType(MediaType.APPLICATION_JSON);
answer = errorJson(ex, isUserLoggedIn());
} else {
logger.info("Returning exception to client as html page");
headers.setContentType(MediaType.TEXT_HTML);
answer = errorHtml(ex, isUserLoggedIn());
}
final HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return new ResponseEntity<>(answer, headers, status);
}
private String errorHtml(Exception e, boolean isUserLoggedIn) {
String error = // html code with exception information here
return error;
}
private Object errorJson(Exception e, boolean isUserLoggedIn) {
// return error wrapper object which will be converted to json
return null;
}
/**
* #param acceptString - HTTP accept header field, format according to HTTP spec:
* "mime1;quality1,mime2;quality2,mime3,mime4,..." (quality is optional)
* #return true only if json is the MIME type with highest quality of all specified MIME types.
*/
private boolean jsonHasPriority(String acceptString) {
if (acceptString != null) {
final String[] mimes = acceptString.split(",");
Arrays.sort(mimes, new MimeQualityComparator());
final String firstMime = mimes[0].split(";")[0];
return firstMime.equals("application/json");
}
return false;
}
private static class MimeQualityComparator implements Comparator<String> {
#Override
public int compare(String mime1, String mime2) {
final double m1Quality = getQualityofMime(mime1);
final double m2Quality = getQualityofMime(mime2);
return Double.compare(m1Quality, m2Quality) * -1;
}
}
/**
* #param mimeAndQuality - "mime;quality" pair from the accept header of a HTTP request,
* according to HTTP spec (missing mimeQuality means quality = 1).
* #return quality of this pair according to HTTP spec.
*/
private static Double getQualityofMime(String mimeAndQuality) {
//split off quality factor
final String[] mime = mimeAndQuality.split(";");
if (mime.length <= 1) {
return 1.0;
} else {
final String quality = mime[1].split("=")[1];
return Double.parseDouble(quality);
}
}
The trick is to have a REST controller with two mappings, one of which specifies "text/html" and returns a valid HTML source. The example below, which was tested in Spring Boot 2.0, assumes the existence of a separate template named "error.html".
#RestController
public class CustomErrorController implements ErrorController {
#Autowired
private ErrorAttributes errorAttributes;
private Map<String,Object> getErrorAttributes( HttpServletRequest request ) {
WebRequest webRequest = new ServletWebRequest(request);
boolean includeStacktrace = false;
return errorAttributes.getErrorAttributes(webRequest,includeStacktrace);
}
#GetMapping(value="/error", produces="text/html")
ModelAndView errorHtml(HttpServletRequest request) {
return new ModelAndView("error.html",getErrorAttributes(request));
}
#GetMapping(value="/error")
Map<String,Object> error(HttpServletRequest request) {
return getErrorAttributes(request);
}
#Override public String getErrorPath() { return "/error"; }
}
References
ModelAndView -- return type for HTML
DefaultErrorAttributes -- data used to render HTML template (and JSON response)
BasicErrorController.java -- Spring Boot source from which this example was derived
The controlleradvice annotation has several properties that can be set, since spring 4. You can define multiple controller advices applying different rules.
One property is "annotations. Probably you can use a specific annotation on the json request mapping or you might find another property more usefull?
Use #ControllerAdvice
Let the exception handler send a DTO containing the field errors.
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
This code is of this website:http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
Look there for more info.

When the validator finds form errors, the form page is redisplayed at the POST url

An item is displayed at this URL:
/item/10101
using this Controller method:
#RequestMapping(value = "/item/{itemId}", method = RequestMethod.GET)
public final String item(HttpServletRequest request, ModelMap model,
#PathVariable long itemId)
{
model = this.fillModel(itemId);
return "item";
}
The page contains a form that submits to the following method in the same controller:
#RequestMapping(value = "/process_form", method = RequestMethod.POST)
public final String processForm(HttpServletRequest request,
#ModelAttribute("foo") FooModel fooModel,
BindingResult bindResult,
ModelMap model)
{
FooModelValidator validator = new FooModelValidator();
validator.validate(FooModel, bindResult);
if (bindResult.hasErrors())
{
model = this.fillModel(fooModel.getItemId());
return "item";
}
return "account";
}
If the validator finds errors in the form, it redisplays the item but instead of displaying it at the original url:
/item/10101
it displays it at its own url:
/process_form
Is it possible to redisplay the form at the original URL?
/item/10101
(I've tried getting the referrer and redirecting to it in processForm but then all of the model contents end up displayed as URL name/value pairs:)
#RequestMapping(value = "/process_form", method = RequestMethod.POST)
public final String processForm(HttpServletRequest request,
#ModelAttribute("foo") FooModel fooModel,
BindingResult bindResult,
ModelMap model)
{
String referrer = request.getHeader("referer");
FooModelValidator validator = new FooModelValidator();
validator.validate(FooModel, bindResult);
if (bindResult.hasErrors())
{
model = this.fillModel(fooModel.getItemId());
return "redirect:" + referrer;
}
return "account";
}
Short answer: No.
What happens is a server-side redirect (forward), which is within the same request, and so the submitted values are preserved (and displayed in the form)
The url will change if you use a client-side redirect (return "redirect:item";), but in that case a new request will come and the submitted values will be lost.
But here are two options that you have:
use the same URL in the mappings for both methods and distinguish them based on request method - GET for the former, POST for the latter. This might be confusing, so document it.
find / implement flash scope for spring-mvc. There's nothing built-in. The flash scope means that values are preserved (in the session usually) for a submit and the subsequent redirect. This option includes the manual handling, by putting the submitted object in the session, and later retrieving & removing it

Categories

Resources