I am developing JAX-RS Rest service using Apache CXF. After deploying it to Tomcat 7 server, if I type the URL http://localhost:8080/Rest/rest?_wadl it shows me the WADL. but if I enter the URL http://localhost:8080/Rest/rest/retrieve it gives me 404 error.
In above URLs: Rest is the name of my project
/rest is the url-pattern for my CXFServlet which is specified in web.xml
/ is the address of jaxrs:server which is specified in beans.xml
retrieve is the path of service which is specified in my interface with #Path annotation.
(My apologies: I can't provide the XML documents referred to above.)
I think this is a CXF bug which get the incorrect base URL for restful web services.
The class "org.apache.cxf.transport.servlet.ServletController" invokes the method "getBaseURL" of the class "org.apache.cxf.transport.servlet.BaseUrlHelper".
It gets the base URL from request URL, and it ignores the parameters part.
This is correct for SOAP web servcies, because SOAP web services URL is just like: http://host:port/basepath?para=a. Unfortunately, for restful web services, the URL is just like http://host:port/basepath/method/parameter. The correct base URL should be http://host:port/basepath, but actually, the BaseUrlHelper gives you http://host:port/basepath/method/parameter. It just gives the URL before "?". It's why the result is correct when you access http://localhost:8080/Rest/rest?_wadl, in this case, it gives the correct base URL http://localhost:8080/Rest.
If you access http://localhost:8080/Rest/rest?_wadl at first then you access http://localhost:8080/Rest/rest/retrieve, it would be correct. Because, CXF set the base URL as the address of EndpointInfo only at the first time. It means, you MUST access the correct base URL at the first time! :(
The solution is: override the method "getBaseURL(HttpServletRequest request)" of "org.apache.cxf.transport.servlet.ServletController", let it return correct base URL.
For example, step1: extends the ServletController.
public class RestfulServletController extends ServletController {
private final String basePath;
public RestfulServletController(DestinationRegistry destinationRegistry, ServletConfig config,
HttpServlet serviceListGenerator, String basePath) {
super(destinationRegistry, config, serviceListGenerator);
this.basePath = basePath;
}
#Override
protected String getBaseURL(HttpServletRequest request) {
// Fixed the bug of BaseUrlHelper.getBaseURL(request) for restful service.
String reqPrefix = request.getRequestURL().toString();
int idx = reqPrefix.indexOf(basePath);
return reqPrefix.substring(0, idx + basePath.length());
}
}
step2: extends CXFNonSpringServlet and use the RestfulServletController in the subclass
public class RestfulCXFServlet extends CXFNonSpringServlet {
... ...
private ServletController createServletController(ServletConfig servletConfig) {
HttpServlet serviceListGeneratorServlet = new ServiceListGeneratorServlet(destinationRegistry, bus);
ServletController newController = new RestfulServletController(destinationRegistry, servletConfig,
serviceListGeneratorServlet, basePath);
return newController;
}
}
step3: instead of CXFNonSpringServlet , you use the derived class RestfulServletController.
Don't forget, you should config the "basePath" as /Rest/rest.
Hope this can help you.
Related
I have set up my spring to maintain a HTTP session on an object like so:
#Component
#SessionScope
public class Basket { .. }
controller:
#PostMapping(path="/basket/addItem/{user}", consumes = "application/json", produces = "application/json")
public Basket createBasket(#PathVariable String user, #RequestBody Item item) {
System.out.println("POSTING..................................");
return basketService.addItem(user, item);
}
now when i use a REST client, in firefox i can see that the session bean is created and maintained for the duration - multiple calls. I can append to the object. If i try another client, it gets its own session with its own bean. great..
spring logs the following:
Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [269] milliseconds.
However im trying to create a basic front end in react, when react makes a request using axios it gets a new bean every time, which means that the session must be ending after each call. IS that correct? or im not tying it to the react application...
Maybe the approach im taking is not correct, maybe i should use a a different approach, Im trying to learn about spring boot, so its a basic project... and right now i want to maintain user session for a cart. so subsequent calls i can append to the object...
by adding the following to my controller it all began to work.
#CrossOrigin(origins = { "http://localhost:3000" }, allowedHeaders = "*", allowCredentials = "true")
I am using the framework in quarkus to build a rest client for the mandrill API
#RegisterRestClient
#Path("1.0")
#Produces("application/json")
#Consumes("application/json")
public interface MailService {
#POST
#Path("/messages/send-template.json")
JsonObject ping(JsonObject mandrillInput);
}
This is the relevant portion of my application.properties
com.example.service.MailService/mp-rest/url=https:/mandrillapp.com/api
And my example resource
#Path("/hello")
public class ExampleResource {
#Inject
#RestClient
MailService mailService;
#Produces(MediaType.TEXT_PLAIN)
#GET
public String hello() {
System.out.print("In the API");
JsonObject key = Json.createObjectBuilder().add("key", "ABCD").build();
System.out.println("The json built is "+key);
JsonObject response = mailService.ping(key);
System.out.println("The response is " + response);
return "hello";
}
}
What I saw is that if the API I am calling (Mandrill in this case) returns an error response (If my key is wrong for example), then the variable I am using to store the response doesnt get the response. Instead the REST API I am exposing to my application wrapping around this, gets populated with the response from Mandrill.
Is this expected behaviour? How can I debug the output of a rest client implementation in Quarkus?
The REST API being called is https://mandrillapp.com/api/docs/users.JSON.html#method=ping2
If you want to be able to get the body of the response when an error occurs, I suggest you use javax.ws.rs.core.Response as the response type.
You could also go another route and handle exceptions using ExceptionMapper
I have rest controller with request mapping as follows:
#PostMapping(value = "fpl/generate/{legIdentifier:.+}"
My camel route is defined as from("direct:/fpl/generate/").
The controller calls web service, web service calls FluentEndpointInvoker class which calls route defined above.
public class FluentEndpointInvoker {
#EndpointInject(uri = BASE_ENDPOINT_URI)
private FluentProducerTemplate producer;
#Value("${server.servlet.context-path}")
private String contextRoot;
public <T, R> T request(Class<T> type, R request, HttpHeaders headers) {
return producer.withProcessor(exchange -> {
exchange.getIn().setBody(request, request.getClass());
headers.forEach((key, value) -> exchange.getIn().setHeader(key, value));
String endpoint = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()
.getRequestURI();
exchange.getIn().setHeader(ROUTE_ENDPOINT_HEADER, "direct:".concat(endpoint.replaceFirst(contextRoot, "")));
}).request(type);
}
}
The endpoint that is generated is something like direct:///fpl/generate/LH.1234.30Jun2016.FRA.BOG.X1. How can I configured wildcards in camel route so that endpoint can get called
Well, if you are not forced to use an endpoint with the full URI, you could simplify the case.
Instead of creating a dynamic endpoint, you could send all messages to the endpoint direct:///fpl/generate and set the full request URI as header on the message.
That way you have a simple route endpoint to use and the URI header to make decisions etc based on the full URI.
I am using Restlet on Google App Engine for developing my sample application.
The front end is Angular 2 App.
The Rest API is working fine with browser.
However, I am getting the following issue when I am trying to hit the URL from Angular app.
XMLHttpRequest cannot load https://1-dot-jda-saas-training-02.appspot.com/rest/projectsBillingInfo/123. The 'Access-Control-Allow-Origin' header contains multiple values 'http://evil.com/, *', but only one is allowed. Origin 'http://localhost:3000' is therefore not allowed access.
So, I thought I will go ahead and add the CORS headers in the response. I used CorsFilter for that as follows but the issue is still there. When I see the header of the Response, I do not see any CORS headers. What am I missing here?
#Override
public Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
CorsFilter corsFilter = new CorsFilter(getContext(), router);
corsFilter.setAllowedOrigins(new HashSet<String>(Arrays.asList("*")));
corsFilter.setAllowedCredentials(true);
// Defines only one route
router.attachDefault(AddressServerResource.class);
router.attach("/contact/123",ContactServerResource.class);
router.attach("/projectsBillingInfo/123",ProjectBillingResource.class);
return corsFilter;
}
EDIT
I could get this working. May be I was doing some mistake.
But, I am not able to make this work with the GaeAuthenticator. When I am putting the GaeAuthenticator along with Corsfilter, it skips the authentication part of it. So, either the authentication works or the corsfilter works but not both. Is there any easy way to set/modify HTTP headers in restlet.
Here is the code I am using ..
#Override
public Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attachDefault(AddressServerResource.class);
router.attach("/contact/123",ContactServerResource.class);
router.attach("/projectsBillingInfo/123",ProjectBillingResource.class);
GaeAuthenticator guard = new GaeAuthenticator(getContext());
guard.setNext(router);
CorsFilter corsFilter = new CorsFilter(getContext(), router);
corsFilter.setAllowedOrigins(new HashSet<String>(Arrays.asList("*")));
corsFilter.setAllowedCredentials(true);
return corsFilter;
First, I think you can use the service instead of the filter:
public MyApplication() {
CorsService corsService = new CorsService();
corsService.setAllowedCredentials(true);
corsService.setSkippingResourceForCorsOptions(true);
getServices().add(corsService);
}
Do you mind to set the "skippingServerResourceForOptions"?
#Override
public Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attachDefault(AddressServerResource.class);
router.attach("/contact/123",ContactServerResource.class);
router.attach("/projectsBillingInfo/123",ProjectBillingResource.class);
return router;
}
Best regards, Thierry Boileau
Following is my App Engine Endpoint. I annotate it as ApiMethod.HttpMethod.GET because I want to be able to make a get call through the browser. The class itself has a few dozen methods understandably. Some of them using POST. But getItems is annotated with GET. When I try to call the url through a browser, I get a 405 error
Error: HTTP method GET is not supported by this URL
The code:
#Api(name = "myserver",
namespace = #ApiNamespace(ownerDomain = "thecompany.com", ownerName = "thecompany", packagePath = ""),
version = "1", description = "thecompany myserver", defaultVersion = AnnotationBoolean.TRUE
)
public class myserver {
#ApiMethod(name = "getItems", httpMethod = ApiMethod.HttpMethod.GET)
public CollectionResponse<Item> getItems(#Named("paramId") Long paramId) {
…
return CollectionResponse.<Item>builder().setItems(ItemList).build();
}
}
This is not for localhost, it’s for the real server. Perhaps I am forming the url incorrectly. I have tried a few urls such as
https://thecompanymyserver.appspot.com/_ah/spi/com.thecompany.myserver.endpoint.myserver.getItems/v1/paramId=542246400
https://thecompanymyserver.appspot.com/_ah/spi/myserver/NewsForVideo/v1/542246400
The proper path for this is /_ah/api/myserver/1/getItems. /_ah/spi refers to the backend path, which only takes POST requests of a different format.
Side note: API versions are typical "vX" instead of just "X".
You can use the api explorer to find out whether you're using the correct url. Go to
https://yourprojectid.appspot.com/_ah/api/explorer
this works on the devserver as well:
http://localhost:8080/_ah/api/explorer
Also if you're not planning to use the google javascript api client you should add path="..." to your #ApiMethods, so you are sure about what the path actually is.