I've got some test code with Jersey (2.7) and Jetty (9.2.5.v2014112) that I'm trying to port to CXF (3.0.x) and Jetty (same version). There's one part that I can't figure out, the equivalent of the rc.register(resource) line below:
ServletContextHandler sch = new ServletContextHandler();
sch.setContextPath("/xxx");
resource = new TheResource();
ResourceConfig rc = new ResourceConfig();
rc.register(resource);
ServletContainer sc = new ServletContainer(rc);
ServletHolder holder = new ServletHolder(sc);
sch.addServlet(holder, "/*");
Server server = new Server(port);
server.setHandler(sch);
server.start();
server.join();
For the CXF/Jetty case I know I need to do something like:
CXFNonSpringJaxrsServlet context = new CXFNonSpringJaxrsServlet();
// EQUIVALENT OF:
// ResourceConfig rc = new ResourceConfig();
// rc.register(myresource)
// SOMETHING.setResourceConfig(rc)
ServletHolder holder = new ServletHolder(context);
ServletContextHandler handler = new ServletContextHandler("/restroot");
handler.addServlet(holder, "/*");
// Using ContextHandlerCollection cos my jetty needs to serve more
// than just this REST service
ContextHandlerCollection collection = new ContextHandlerCollection();
collection.addHandler(handler);
Server server = new Server(8080);
server.setHandler(collection);
server.start();
server.join();
Does anybody know how/if this is possible?
Just to clarify what I'm trying to do here: I'm trying to publish a JAX-RS service on an embedded Jetty instance, and I need full control over the lifecycle of the JAX-RS bean.
Maarten
From CXF version 3.0.4 onwards, there are additional constructors on the CXFNonSpringJaxrsServlet that make this a lot easier:
public SNAPSHOTApp(int port) throws Exception {
resource = new TheResource();
Set<Object> resourceSingletons = new HashSet<>();
resourceSingletons.add(resource);
CXFNonSpringJaxrsServlet context = new CXFNonSpringJaxrsServlet(resourceSingletons);
ServletHolder servlet = new ServletHolder(context);
ServletContextHandler handler = new ServletContextHandler();
handler.addServlet(servlet, "/*");
handler.setContextPath("/snapshot");
Server server = new Server(port);
server.setHandler(handler);
server.start();
server.join();
}
Big thanks to #Sergey Beryozkin for adding this based on my question on the CXF user mailing list.
Look at the source for CXFNonSpringJaxrsServlet, it is configured via init params from web.xml. However you're free to override that functionality of course
I've managed to get something working by:
extending CXFNonSpringJaxrsServlet
Overriding getResourceProviders()
Providing my own implementation of ResourceProvider
It all looks a bit complex, and I feel that there should be a standard API for this in CXF, but anyways, here's my solution:
public class TheApp {
private static final Logger LOG = LoggerFactory.getLogger(TheApp.class);
TheResource resource;
public static class BeanResourceProvider implements ResourceProvider {
private final Object theBean;
public BeanResourceProvider(Object theBean) {
this.theBean = theBean;
}
#Override
public Object getInstance(Message m) {
return theBean;
}
#Override
public void releaseInstance(Message m, Object o) {
}
#Override
public Class<?> getResourceClass() {
return theBean.getClass();
}
#Override
public boolean isSingleton() {
return true;
}
};
public TheApp(int port) throws Exception {
resource = new TheResource();
CXFNonSpringJaxrsServlet context = new CXFNonSpringJaxrsServlet() {
public void configureSingleton(Object o) {
LOG.info("configureSingleton() called");
}
public Map<Class<?>, ResourceProvider>
getResourceProviders(ServletConfig servletConfig,
Map<Class<?>, Map<String, List<String>>> resourceClasses) throws ServletException {
LOG.info("getResourceProviders called");
Map<Class<?>, ResourceProvider> map = new HashMap<Class<?>, ResourceProvider>();
for (Map.Entry<Class<?>, Map<String, List<String>>> entry : resourceClasses.entrySet()) {
Class<?> c = entry.getKey();
LOG.info("getting provider for {}", c.getName());
map.put(c, new BeanResourceProvider(resource));
}
return map;
}
};
ServletHolder holder = new ServletHolder(context);
holder.setInitParameter("jaxrs.serviceClasses", "com.test.cxfjetty.TheResource");
ServletContextHandler handler = new ServletContextHandler();
handler.addServlet(holder, "/*");
handler.setContextPath("/theroot");
Server server = new Server(port);
server.setHandler(handler);
server.start();
server.join();
}
public static void main(String[] args) throws Exception {
TheApp app = new TheApp(8122);
}
}
Fun exercise this was...
Related
My goal is to configure a Jetty server to work with the Vaadin 11 framework.
What I was trying was the following:
public static void main(final String[] args) {
final Server server = new Server();
final ServerConnector httpConnector = new ServerConnector(server);
httpConnector.setPort(8080);
final ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
final ServletHolder servletHolder = new ServletHolder(new VaadinServlet());
contextHandler.addServlet(servletHolder, "/*");
final WebAppContext context = new WebAppContext();
context.setServer(server);
context.setContextPath("/");
context.setClassLoader(Thread.currentThread().getContextClassLoader());
server.addConnector(httpConnector);
server.setHandler(contextHandler);
server.setHandler(context);
try {
server.start();
} catch (final Exception e) {
e.printStackTrace();
}
}
#Route("/")
public class MainView extends Div {
public MainView() {
System.out.println("main");
setText("aha bye");
}
}
But the MainView is never called.
How can I tell Jetty to forward the requests to Vaadin?
Answering my own question here.
But this will still not work as you expect. Why? Because you have 2 different contexts (contextHandler and context) on the same contextPath /.
But again, this seems backwards, as your WebAppContext has no resource base or war declared, so it's not doing anything for you in your sample code.
Joakim Erdfelt is absolutely right. I messed up the code with two contexts and I either have to set a resource base or a war-file. Many thanks to you as you pointed out the way to the solution!
Thanks to Leif Åstrand too for the hint of finding all classes with the #Route annotation!
I separated the server code from the "client" code and build the war-file for the jetty server.
What I use as a working solution is the following:
final WebAppContext context = new WebAppContext();
context.setServer(httpServer);
context.setContextPath("/");
context.addServlet(new ServletHolder(new TestServlet()), "/*");
context.setWar(warFile.getAbsolutePath());
context.setExtractWAR(true);
context.setClassLoader(Thread.currentThread().getContextClassLoader());
context.setInitParameter("ui", MainUI.class.getCanonicalName());
httpServer.setHandler(context);
In the Testservlet (extending VaadinServlet) I have the following two methods:
#SuppressWarnings("unchecked")
private void findRouteClasses() {
final Set<Class<?>> routeClasses = ReflectionUtils.findClassesWithAnnotation(Route.class,
"com.example.myproject");
this.routeClasses = new HashSet<>();
for (final Class<?> clazz : routeClasses)
this.routeClasses.add((Class<? extends Component>) clazz);
}
#Override
protected void servletInitialized() throws ServletException {
final ServletContext context = getServletContext();
final Object routeRegistryObject = context.getAttribute(RouteRegistry.class.getName());
if (routeRegistryObject == null)
throw new ServletException("routeRegistryObject is null");
if ((routeRegistryObject instanceof RouteRegistry) == false)
throw new ServletException("routeRegistryObject is not of type RouteRegistry");
final RouteRegistry routeRegistry = (RouteRegistry) routeRegistryObject;
findRouteClasses();
try {
routeRegistry.setNavigationTargets(routeClasses);
} catch (final InvalidRouteConfigurationException e) {
throw new ServletException(e);
}
super.servletInitialized();
}
All classes with the annotation com.vaadin.flow.router.Route are seached and then set as the navigation targets.
server.setHandler(contextHandler);
server.setHandler(context);
You replaced your contextHandler with the context with that code.
Try this instead ...
HandlerList handlers = new HandlerList();
handlers.addHandler(contextHandler);
handlers.addHandler(context);
handlers.addHandler(new DefaultHandler()); // to report errors if nothing above matches
server.setHandler(handlers);
But this will still not work as you expect.
Why? Because you have 2 different contexts (contextHandler and context) on the same contextPath /.
The first context in your HandlerList will be used, and the next one will never be called.
Why? Because once you enter a context, you do not exit it (that context MUST serve a response, even an error).
You could just modify your WebAppContext to include the VaadinServlet (eliminating the need for ServletContextHandler entirely)
eg:
final WebAppContext context = new WebAppContext();
final ServletHolder servletHolder = new ServletHolder(new VaadinServlet());
context.addServlet(servletHolder, "/*");
But again, this seems backwards, as your WebAppContext has no resource base or war declared, so it's not doing anything for you in your sample code.
If it were me, and I'm using embedded-jetty, I would avoid using a WebAppContext entirely and just stick with the ServletContextHandler only.
I am trying to build an api gateway on top of an old Solr server with zuul.
I want to have multiple routes such as :
api-zuul/api1
api-zuul/api2
api-zuul/api3
redirecteing to the same endpoint (Solr api) but passing different parameters depending on the call.
api-zuul/api1 => mysolrserver/api/select?&collection=api1&otherParams
api-zuul/api2 => mysolrserver/api/select?&collection=api2&otherParams
api-zuul/api3 => mysolrserver/api/select?&collection=api3&otherParams
At the moment i manage to pass parameters via a ZuulFilter to only one route:
public class ProjectParamFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(ProjectBodyFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
Map<String, List<String>> params = new HashMap<>();
List<String> paramAll = new ArrayList<>();
List<String> paramTrue = new ArrayList<>();
List<String> paramJson = new ArrayList<>();
List<String> paramCollection = new ArrayList<>();
paramAll.add("*:*");
paramTrue.add("true");
paramJson.add("json");
paramCollection.add("Project");
params.put("q", paramAll);
params.put("indent", paramTrue);
params.put("wt", paramJson);
params.put("collection", paramCollection);
ctx.setRequestQueryParams(params);
log.info("passed params :" + ctx.getRequestQueryParams().toString());
log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
return null;
}
}
I don't understand how i can call different filters for the same endpoint depending on the incoming request since zuul is designed to handle multiple services.
If anyone come across the same issue, i solved it using the shouldFilter() function.
Add a route in application.properties :
zuul.routes.test.url = https://stackoverflow.com/
In the filter :
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getRequest().getRequestURI().equals("/test")) {
return true;
} else {
return false;
}
}
I write a jettyserver.java as start service,here is the code,
public class jettyserver {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
ServletHolder servletHolder = new ServletHolder(ServletContainer.class);
Map<String, String> parameterMap = new HashMap<String, String>();
parameterMap.put("jersey.config.server.provider.packages", "com.heu.cs.mavenproject3");
servletHolder.setInitParameters(parameterMap);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/jettyproject/*");
context.addServlet(servletHolder, "/*");
server.setHandler(context);
server.start();
server.join();
}
}
And I write a demo.java as a test demo,
#Path("/demo")
public class demo {
#GET
#Produces({"text/html"})
public String index(){
return "OK";
}
}
and then i run jettyserver.java,I got OK like:
we can see OK,that's right
but wen I enter localhost:8080/jettyproject/index.html or localhost:8080/index.html in the browser, I got a 404 error like this:
what should I do ? What'wrong with the code????
I have an endpoint with:
#POST
#Path("/test")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public String canaryTest(String JSON) {
return JSON;
}
When I register it in Jetty using Jersey
ServletHolder holder = new ServletHolder(new ServletContainer());
everything seems to work fine.
But in case I try to specify explictly the default config, it stops working (returning a media type error from the endpoint). Even by just passing a default instance of a ResourceConfig to the ServletContainer, it stops working.
ResourceConfig config = new ResourceConfig();
//config.property(x,defaultX)
//config.property(y,defaultY)
ServletHolder holder = new ServletHolder(new ServletContainer(config));
I'd like to emulate the default configuration behavior manually and explicitly, so what I am asking here is how should I configure ResourceConfig so the behavior keeps working (i.e, what properties to set)
P.S: i'm using Jetty 9.2.6.v20141205 and Jersey 2.14.
Dependencies in Maven:
org.eclipse.jetty.jetty-server org.eclipse.jetty.jetty-servlet
org.eclipse.jetty.jetty-servlets
org.glassfish.jersey.containers.jersey-container-servlet-core
com.sun.jersey.jersey-json
org.glassfish.jersey.media.jersey-media-json-jackson
I don't know how you got this to work
ServletHolder holder = new ServletHolder(new ServletContainer());
I could not produce a working example simply instantiating the ServletContainer(). Though I was about to get it to work with the following code
public class TestJerseyServer {
public static void main(String[] args) throws Exception {
ResourceConfig config = new ResourceConfig();
config.packages("jetty.practice.resources");
ServletHolder jerseyServlet
= new ServletHolder(new ServletContainer(config));
Server server = new Server(8080);
ServletContextHandler context
= new ServletContextHandler(server, "/");
context.addServlet(jerseyServlet, "/*");
server.start();
server.join();
}
}
Using all your dependencies, excluding the com.sun.jersey:jersey-json, as it's not needed. No other configuration. The resource class
#Path("test")
public class TestResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getTest() {
Hello hello = new Hello();
hello.hello = "world";
return Response.ok(hello).build();
}
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response postHello(Hello hello) {
return Response.ok(hello.hello).build();
}
public static class Hello {
public String hello;
}
}
in the jetty.practice.resources package.
I'm curious to see how you got it to work without the ResourceConfig
Another thing I should mention is that jersey-container-servlet-core should be switched out for jersey-container-servlet. The former is for 2.5 container support, but the latter is recommended for 3.x containers. It not have any effect though, with my example
cURL
C:\>curl http://localhost:8080/test -X POST -d "{\"hello\":\"world\"}" -H "Content-Type:application/json"
world
C:\>curl http://localhost:8080/test
{"hello":"world"}
I am working on a web application that will run on a Jetty server. I want to be able to put an object into a hashmap from the Application itself and then read back the same Object using a Client. I created a Singleton instance of the HashMap in both the application and the client thinking it would be the same instance, but after printing both Objects to the console, I am seeing that they are two separate instances and thus do not contain the same data. Does anyone know how to make the HashMap accessible from the app and from the client? Looking at this post, Singleton is not really a singleton I see that I should be using org.eclipse.jetty.webapp.WebAppContext to set the parent loader but am not exactly sure how to approach this. Here is what I have so far.
Server:
public static void main(String[] args) throws Exception {
Server server = new Server(8888);
ServletHandler handler = new ServletHandler();
handler.addServletWithMapping(App.class, "/S3Mock");//Set the servlet to run.
server.setHandler(handler);
server.start();
server.join();
}
App:
#SuppressWarnings("serial")
public class App extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
S3Controller s3cont = new S3Controller();
Map appMap = Storage.getInstance();
s3cont.putObject(appMap, "aws.amazon.com/buckets/mybucket", new Object());
s3cont.putObject(appMap, "aws.amazon.com/buckets/mybucket2", new Object());
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<h2>AWS S3</h2>");
}
}
Client:
public static void main(String[] args) {
Client client = Client.create();
//Get
WebResource webResource = client.resource("http://localhost:8888/S3Mock");
ClientResponse response = webResource.accept(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON)
.get(ClientResponse.class);
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ response.getStatus());
}
S3Controller s3cont = new S3Controller();
Map m = Storage.getInstance();
Object b = s3cont.getObject(m, "aws.amazon.com/buckets/mybucket2");
if(b != null)
System.out.println(b.toString());
else
System.out.println("Object not found");
}
Storage:
public class Storage {
private static Map<String, Object> theMap = new HashMap<String, Object>();
private Storage() {}
public static Map getInstance() {
return theMap;
}
}
After firing up the Jetty server, when I run the client, the console reads, Object not found which tells me that that there are, in fact, two separate HashMap instances.