Following the example provided by spring.io for creating a simple REST service, I run into a strange issue.
If I make a request to localhost:8080/greeting the greeting route is called and I receive the expected response:
{"id":1, "content":"Hello, World!"}
If I add a route "/test" and then make an HTTP GET request to localhost:8080/test I get the expected response:
I'm a teapot
The problem arises when I do one of two things. Firstly, if I add HttpServletResponse or HttpServletRequest as a parameter to the test route and make an HTTP GET request to localhost:8080/test, the request hangs, the route is not called/executed, and maybe but not always the following is returned:
BODY: OK STATUS CODE: UNKNOWN 43
The second case is when I try to overcome this by using the #Autowire annotation. If I remove the HttpServletResponse/HttpServletRequest method parameters and instead autowire them in as class members, I get the same behavior.
If I make a request to any other invalid/undefined route e.g. HTTP GET localhost:8080/undefinedroute I receive the expected 404.
package hello;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
//#Autowired
//HttpServletRequest request;
//#Autowired
//HttpServletResponse response;
#RequestMapping("/test")
public String index() {
return HttpStatus.I_AM_A_TEAPOT.getReasonPhrase();
}
//#RequestMapping("/test")
//public String index(HttpServletResponse response) {
//response.setStatus(HttpStatus.I_AM_A_TEAPOT.ordinal());
//return HttpStatus.I_AM_A_TEAPOT.getReasonPhrase();
//}
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
You cannot autowire HttpServletRequest or HttpServletResponse, because these objects are created when the server receives and handles an HTTP request. They are not beans in the Spring application context.
The status code of your response is 43 (unknown) because of this line:
response.setStatus(HttpStatus.I_AM_A_TEAPOT.ordinal()); // status 43?
ordinal() gives you the position of enum declaration, not the value of the status code. I_AM_A_TEAPOT is the 43rd enum declared in HttpStatus. The request hangs because 43 is an invalid status code and your browser does not know how to deal with it. You should use:
response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); // status 418
Related
I am using some external API to GET and POST some ressources, locally my code works fine with the call of different endPoints (GET, POST...) and even with Postman, but when i try to run my code in another platerform (where the ressources are), i get the 412 HTTP error due to a POST call : after looking on the internet, i found out that i should generate an ETagd of the entity (that i went to modify) and add it into the header of my POST endPoint.
For that, i used ShallowEtagHeaderFilter and the #Bean annotation(above the filter method) and the #SpringBootApplication annotation above my class, here is my code :
package main.Runners;
import io.testproject.java.annotations.v2.Parameter;
import okhttp3.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.configurationprocessor.json.JSONArray;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.context.annotation.Bean;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import javax.servlet.Filter;
#SpringBootApplication
public class ActionRunner {
#Parameter(description = "the project ID")
public static String projectId = "xxx";
#Parameter(description = "the test ID")
public static String testId = "yyy";
public static void main(String[] args) throws Exception {
try {
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
Request request = new Request.Builder()
.url("https://api.testproject.io/v2/projects/"+projectId+"/tests/"+testId)
.method("GET", null)
.addHeader("Authorization", "nzmo4DI08ykizYgcp9-5cCTArlxq7k7zt9MYhGmTcRk1")
.build();
Response response = client.newCall(request).execute();
System.out.println("================ this is our response headers ::: \n"+ response.headers());
} catch(Exception e) {
System.out.println(e);
}
}
#Bean
public ShallowEtagHeaderFilter shallowEtagHeaderFilter(){
return new ShallowEtagHeaderFilter();
}
}
I really need Your help since i cant generate any ETag parameter on my GET response header(after checking reponse.headers() ).
Thanks in advance!
I have a RestController defined as follows:
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.PostMapping
import javax.validation.Valid
#RestController
class CaseController(
private val caseService: CaseService,
private val remoteFileService: RemoteFileService,
private val clientService: ClientService
) {
#PostMapping("/api/v1/cases", consumes = [MediaType.APPLICATION_JSON_VALUE])
#NeedsAuth
fun createCase(
#RequestBody #Valid caseCreationRequest: CaseCreationRequest,
#RequestHeader("Api-Key", required = false) apiKey: String,
): ResponseEntity<Case> { }
I have defined NeedsAuth as an annotation.
The problem is that the #Valid annotation is being called before #NeedsAuth.
If I send invalid request body with invalid authentication, I receive "Validation Error" as response.
If I send valid request body with invalid authentication, I receive "Authentication Error".
If I remove #Valid annotation from code and then send invalid request body with invalid authentication, I receive "Authentication Error".
What I want this to do?
I want it to call #NeedsAuth before #Valid.
Any help is greatly appreciated.
Thanks
Update:
Code related to handling of #NeedsAuth:
//NeedsAuth.kt
package com.jimdo.debtcollectionservice.adapters.apis.http.auth
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class NeedsAuth
//AuthAspect.kt
package com.jimdo.debtcollectionservice.adapters.apis.http.auth
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
#Component
#Aspect
class AuthAspect {
#Autowired
lateinit var authTokenHandler: AuthTokenHandler
#Before("execution(* *.*(..)) && #annotation(NeedsAuth)")
fun validateToken(joinPoint: JoinPoint) {
val request = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request
authTokenHandler.authenticateToken(request.getHeader("Api-Key"))
}
}
I'm trying to find a way to retrieve the URL that is currently mapped by Feign Client method interface in a Spring app, but I could not find a solution yet. The following code works:
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
application.yml
api:
url: https://jsonplaceholder.typicode.com
ApiClient.class
package com.example.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
#FeignClient(name = "json-placeholder", url = "${api.url}")
public interface ApiClient {
#GetMapping(value = "/posts", consumes = "application/json")
ResponseEntity<String> getPosts();
}
FeignApplication.class
package com.example.feign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import javax.annotation.PostConstruct;
#EnableFeignClients
#SpringBootApplication
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
#Autowired
private ApiClient apiClient;
#PostConstruct
public void test() {
// this works
System.out.println(apiClient.getPosts().getBody());
// apiClient. ... getPosts request URL
}
}
I tried to compose this URL reading directly from annotations, but it doesn't seem to work. Can anybody give me an alternative? Thank you.
EDIT notes -
Sorry, but I had do a small change in my question, due to an unexpected problem while applying provided solutions.
Reading directly from Annotations works if the value set in annotation property is literal. If the value is read from application.yml file, the URL returned by the annotation property is the expression ifself, not the parsed value.
Any ideas about this updated scenario? What I need is the URL actually been called by FeignClient. I'm understands all provided solutions are actually workarounds.
I'm not sure if you are still looking for the answer, but my method is below
Return Response
import feign.Response;
#FeignClient(name = "json-placeholder", url = "${api.url}")
public interface ApiClient {
#GetMapping(value = "/posts", consumes = "application/json")
Response getPosts();
}
get Request.url from the Response
Response response = apiClient.getPosts();
String url = response.request().url()
then you can get the URL
I would like to test the spring controller below, which reads http request attributes and acts on them. I am able to trigger the controller code below by typing localhost:8080/someURL into my web browser. But the result is {"id":1,"content":"null and null and null"}, which indicate null values in the named request attributes. How do I send a request to a named url like localhost:8080/someURL which contains values for the specified request attributes, so that I can confirm that the receiving controller code works properly?
Here is the code for the controller:
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestPa ram;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
#Controller
public class SomeController {
private final AtomicLong counter = new AtomicLong();
#RequestMapping(value = "/someURL", method=RequestMethod.GET)
public #ResponseBody Greeting receiveSMS(HttpServletRequest req){
String att1 = (String) req.getAttribute("att1");
String att2 = (String) req.getAttribute("att2");
String att3 = (String) req.getAttribute("att3");
String total = att1 + " and " + att2 + " and " + att3;
return new Greeting(counter.incrementAndGet(), String.format(total));
}
}
Note: I am trying to recreate in Spring the PHP functionality that is given in the script at the following link. I have not written this kind of code below, if I am framing the question poorly, I would appreciate advice for reframing it. Along with a link to any example solution such as a JUNIT or other means by which to recreate the request.
Request attributes are server-side only constructs. Try using request parameters instead:
#RequestMapping(value = "/someURL", method = RequestMethod.GET)
public #ResponseBody Greeting receiveSMS(#RequestParam("att1") String att1, #RequestParam("att2") String att2, #RequestParam("att3") String att3){
String total = String.format("%s and %s and %s", att1, att2, att3);
return new Greeting(counter.incrementAndGet(), total);
}
Then send a request of the form:
http://localhost:8080/someURL?att1=value1&att2=value2&att3=value3
And you should be able to read the values that you are trying to pass.
Checkout Spring MVC Test framework - instead of manually fire some URLs write unit tests instead.
Regarding your note
Yes, that's parameters. In php you have $_GET and $_POST or (if you don't care about the method) simply $_REQUEST for accessing the request parameters. Recode from getAttribute() to getParameter() or put them in your method signature using #RequestParam annotation.
#RequestMapping(value = "/receiveSMS", method=RequestMethod.GET)
public #ResponseBody Greeting receiveSMS(#RequestParam("from") String from,
#RequestParam("to") String to, #RequestParam("body") String body){
}
Now you can try http://localhost:8080/yourapp/receiveSMS?from=me&to=you&body=stackoverflow
Sidenote:
If you want that info send from the client, you should use getParameter() calls instead.
test something like :
http://localhost:8080/someURL?att1=value1&att2=value2&att3=value3
It will show you the value1, value2 and value3 passed into the URL...
Currently, when successfully connecting to an endpoint, I am passing a custom token (called access-token) back to the client via a response header. This header is being set correctly and I can verify the header by analyzing the HTTP response.
However, when trying to get the header from the frame object the header is not set (see the JavaScript below):
stompClient.connect(headers,
function(frame) {
console.log('=========================================');
console.log(frame.headers['access-token']);
console.log(frame);
console.log('=========================================');
stompClient.subscribe('/topic/test', function(stuff){
console.debug(stuff);
});
},
function(error) {
//error code
}
);
I am setting the response header as follows on the server:
public class HttpSessionHandshakeInterceptorImpl extends HttpSessionHandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
response.getHeaders().set("access-token", token);
return super.beforeHandshake(request, response, wsHandler, attributes);
I stripped out some code and can confirm that the interceptor is being called. I take it that this is not the correct way to pass a header value back to the client when the connect function is called? I can't seem to find any documentation on how to accomplish this. Thanks.
ServerHttpResponse response - this is the response to a HTTP request, which you can inspect in more detail (by logging or in debug mode). Setting its headers will affect the HTTP response, not the STOMP stream which flows over the WebSocket connection. The STOMP headers of the CONNECTED are set by the message broker.
I do not know it for sure but I doubt that Spring is capable of inserting extra headers in the STOMP frames. (please correct me if I am wrong)
You need to implement ChannelInterceptor and register it with output bound channel
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.MultiValueMap;
#Configuration
public class WebSocketServiceCustomHeaderInterceptor implements ChannelInterceptor {
private static final Logger logger = LoggerFactory.getLogger(WebSocketServiceCustomHeaderInterceptor.class);
#Override
#Nullable
public Message<?> preSend(Message<?> message, MessageChannel channel) {
final StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);
final StompCommand command = headerAccessor.getCommand();
if (command != null && command == StompCommand.CONNECTED) {
final StompHeaderAccessor accessor = StompHeaderAccessor.create(command);
accessor.setSessionId(headerAccessor.getSessionId());
#SuppressWarnings("unchecked")
final MultiValueMap<String, String> nativeHeaders = (MultiValueMap<String, String>) headerAccessor
.getHeader(StompHeaderAccessor.NATIVE_HEADERS);
accessor.addNativeHeaders(nativeHeaders);
// add custom headers
try {
accessor.addNativeHeader("HOSTNAME", InetAddress.getLocalHost().getHostName());
} catch (UnknownHostException e) {
logger.error("Error getting host name ", e);
}
final Message<?> newMessage = MessageBuilder.createMessage(new byte[0], accessor.getMessageHeaders());
return newMessage;
}
return message;
}
}
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Bean
public WebSocketServiceCustomHeaderInterceptor webSocketServiceCustomHeaderInterceptor() {
return new WebSocketServiceCustomHeaderInterceptor();
}
#Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.interceptors(webSocketServiceCustomHeaderInterceptor());
registration.taskExecutor().corePoolSize(outboundPoolCoreSize).maxPoolSize(outboundPoolMaxSize);
}
}
I'm afraid this is not possible without a major hack. The issue is, the headers are completely rebuilt AFTER the ChannelInterceptors are called. See method StompSubProtocolHandler.convertConnectAcktoStompConnected
The method StompSubProtocolHandler.handleMessageToClient will also add a few headers depending on the type of message, for example by calling afterStompSessionConnected which sets the header "user-name" if the authentication is done.
I've tried adding headers as well but I'm about to give up... My guess is that the people who wrote the STOMP implementation in Spring wanted to strictly respect the specifications