I've configured my servlets/filters using guice-servlet. With bindings like
serve("/foo").with(HelloServlet.class);
Now, I want to test that mapping. I've used jetty-testing
private ServletTester tester;
private HttpTester request;
private HttpTester response;
#BeforeMethod
public void setUp() {
this.tester = new ServletTester();
this.tester.setContextPath("/");
this.tester.addEventListener(new Config()); //my guice servlet config goes there.
this.tester.addFilter(GuiceFilter.class, "/*", 0);
this.tester.addServlet(FakeServlet.class, "/*"); <-----!!!!
this.tester.start();
this.request = new HttpTester();
this.response = new HttpTester();
this.request.setMethod("GET");
this.request.setHeader("Host", "tester");
this.request.setVersion("HTTP/1.0");
}
#Test
public void test() {
this.request.setURI("/foo");
this.response.parse(tester.getResponses(request.generate()));
assertEquals(this.response.getContent(), "Hello World");
}
It works. But it made me to add some fake servlet that should bewer be invoked. How I can test it without adding such servlet?
The servlet spec requires a servlet for the doFilter chain to make sense.
If you don't want to create your own Servlet in the test cases, just use the org.eclipse.jetty.servlet.DefaultServlet.class instead (found in the jetty-servlet.jar).
Related
I am currently writing an application in Spring Boot 2.4.0 that is required to listen on multiple ports (3, to be specific - but might be 4 in the future). The idea is that each port makes a different API available for other services/apps to connect to it.
So, for a minimal working example, I'd say we have a SpringBootApp like this:
#SpringBootApplication
public class MultiportSpringBoot {
public static void main(String[] args)
{
SpringApplication.run(MultiportSpringBoot.class, args);
}
}
Now, I'd want to have this listening on 3 different ports, say 8080, 8081, and 8082. For all (!) requests to one of these ports, a specific controller should be "in charge". One of the reasons for this requirement is that one controller needs to handle a (regular) web frontend and another an API. In case an invalid request is received, the API-controller needs to give a different error message than the frontend should. Hence, the requirement given is a clear separation.
So I imagine multiple controllers for the different ports, such as:
#Controller
public class Controller8080
{
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView test8080()
{
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("test8080");
return modelAndView;
}
}
with similar controllers for the other ports:
#Controller
public class Controller8081
{
#RequestMapping(value = "/", method = RequestMethod.GET)
public ResponseEntity test8081()
{
JSONObject stuff = doSomeStuffForPort8081();
return new ResponseEntity<String>(stuff, HttpStatus.OK);
}
}
I hoped for an annotation similar to #RequestMapping to be able to match and fix the port numbers for the controllers, but this seems to be no option as no such annotation seems to exist.
Now, this topic seems to be a bit specific, which is probably why you don't find all too much info on the web. I found Starting Spring boot REST controller in two ports, but I can also only have ONE instance running. I looked at https://tech.asimio.net/2016/12/15/Configuring-Tomcat-to-Listen-on-Multiple-ports-using-Spring-Boot.html, but this is outdated for Spring Boot 2.4.0 and a bit bloated with JavaMelody examples.
Anyone can provide a minimum working example for a solution for this?
--
EDIT:
To clarify a bit more: I need multiple, separate RESTControllers that each handle requests on different ports. I.e. a request to domain.com:8080/ should be handled by a different controller than a request to domain.com:8081/.
As an example, consider the two following controllers that should handle requests on ports 8080 and 8081 respectively:
//controller for port 8080
#RestController
public class ControllerA
{
#GetMapping("/")
String helloA(HttpServletRequest request)
{
return "ControllerA at port " + request.getLocalPort();
}
}
and
//controller for port 8081
#RestController
public class ControllerB
{
#GetMapping("/")
String helloB(HttpServletRequest request)
{
return "ControllerB at port " + request.getLocalPort();
}
}
The tomcat class names changed a little bit so the link you provide has the old code but it is enough for the new code. Code below shows how you can open multiple ports in spring boot 2.4
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(additionalConnector());
return tomcat;
}
private Connector[] additionalConnector() {
if (!StringUtils.hasLength(this.additionalPorts)) {
return null;
}
String[] ports = this.additionalPorts.split(",");
List<Connector> result = new ArrayList<>();
for (String port : ports) {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(Integer.valueOf(port));
result.add(connector);
}
return result.toArray(new Connector[]{});
}
And for responding to different ports with different controller you can implement the logic like check getLocalPort and respond it accordingly.
#GetMapping("/hello")
String hello(HttpServletRequest request) {
return "hello from " + request.getLocalPort();
}
Or you can write a logical controller in filter. example code below
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getLocalPort() == 8882 && req.getRequestURI().startsWith("/somefunction")) {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
} else {
fc.doFilter(request, response);
}
}
You can find all running example here https://github.com/ozkanpakdil/spring-examples/tree/master/multiport
This is how it looks in my local
In order to have same path with different controllers you can use #RequestMapping("/controllerNO") on top of the classes(check), NO should be number 1 , 2, otherwise spring will complain "you have same path" and will give you this exception
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'testController2' method
com.mascix.multiport.TestController2#hello(HttpServletRequest)
to {GET [/hello]}: There is already 'testController1' bean method
Because from design spring will allow only one path to correspond to one controller, after requestmapping you can change the filter as this. Good thing about reflection you will learn very different exceptions. java.lang.NoSuchMethodException or java.lang.IllegalArgumentException
Latest code how it works in my local
I must say this approach is not right and against the design of spring, in order to have different ports with different controllers, have multiple JVMs. If you mix the logic it will be harder for you to solve future problems and implement new features.
If you have to do it in one jvm, write a service layer and call the functions separately from one controller and write a logic like below
#GetMapping("/hello")
String hello(HttpServletRequest request) {
if (request.getLocalPort() == 8888) {
return service.hellofrom8888();
}
if (request.getLocalPort() == 8889) {
return service.hellofrom8889();
}
return "no repsonse ";
}
At least this will be easy to maintain and debug. Still looks "ugly" though :)
Özkan has already provided detailed information on how to get Tomcat to listen to multiple ports by supplying your own ServletWebServerFactory #Bean based on TomcatServletWebServerFactory.
As for the mapping, how about this approach:
Add a #RequestMapping("/8080") to your controller (methods keep their specific #RequestMapping)
#Controller
#RequestMapping("/8080")
public class Controller8080
{
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView test8080()
{
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("test8080");
return modelAndView;
}
}
Define your own RequestMappingHandlerMapping as
public class PortBasedRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
#Override
protected HandlerMethod lookupHandlerMethod(final String lookupPath, final HttpServletRequest request) throws Exception {
return super.lookupHandlerMethod(request.getLocalPort() + lookupPath, request);
}
}
and use it by
#Bean
public WebMvcRegistrations webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrations() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new PortBasedRequestMappingHandlerMapping();
}
};
}
This will attempt to map a request to /foobar on port 8080 to /8080/foobar.
Another approach is by using org.springframework.web.servlet.mvc.condition.RequestCondition which I think is cleaner https://stackoverflow.com/a/69397870/6166627
I'm trying to implement a restful web service using Jersey JAX-RS.
I embedded a Jetty web server and wanted to register all the controllers on it.
I based on this example:
https://nikgrozev.com/2014/10/16/rest-with-embedded-jetty-and-jersey-in-a-single-jar-step-by-step/
in which EntryPoint is the controller:
#Path("/entry-point")
public class EntryPoint {
#GET
#Path("test")
#Produces(MediaType.TEXT_PLAIN)
public String test() {
return "Test";
}
}
and this is registered using the key name "jersey.config.server.provider.classnames" as follows:
public class App {
public static void main(String[] args) throws Exception {
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
Server jettyServer = new Server(8080);
jettyServer.setHandler(context);
ServletHolder jerseyServlet = context.addServlet(
org.glassfish.jersey.servlet.ServletContainer.class, "/*");
jerseyServlet.setInitOrder(0);
// Tells the Jersey Servlet which REST service/class to load.
jerseyServlet.setInitParameter(
"jersey.config.server.provider.classnames",
EntryPoint.class.getCanonicalName());
try {
jettyServer.start();
jettyServer.join();
} finally {
jettyServer.destroy();
}
}
}
How can I register many controllers?
If I add other controller classes as params I don't know what key name I must give to each one, because only "jersey.config.server.provider.classnames" seems to work and works once.
Thanks.
Because you can only use the property once, you need to use a comma delimited list as the value classOne, classTwo, classThree.
Another option is to use the property jersey.config.server.provider.packages and just give it a package to recursively scan
jerseyServlet.setInitParam(ServerProperties.PROVIDER_PACKAGES, "my.package.to.scan");
See ServerProperties for more properties you can set. Here PROVIDER_PACAKGES is a constant, whose string value is jersey.config.server.provider.packages. Same with the classnames property there is a constant PROVIDER_CLASSNAMES.
By declaring the package to scan, Jersey will scan that package recursively (by default) and register all #Path and #Provider annotated classes it finds in the scan.
I want to test my Jersey resources in a Dropwizard 0.8.0-rc2 application. An additional obstacle is that I use TestNG instead of JUnit, so I have to do some things by hand that I copied from DropwizardClientRule.
Now I have some resources that are secured by #Auth. In Dropwizard 0.7.1 I added the authenticator to the test application as follows:
DropwizardResourceConfig resourceConfig = DropwizardResourceConfig.forTesting(…);
Authenticator<BasicCredentials, Identity> authenticator =
credentials -> getAuthorizedIdentity();
resourceConfig.getSingletons().add(
new BasicAuthProvider<>(authenticator, "TEST"));
Here, getAuthorizedIdentity() would fetch some test authorization. This would be passed to the #Auth annotated injected parameter. Now, of course, with Jersey 2 things have changed a bit. I tried:
DropwizardResourceConfig resourceConfig = DropwizardResourceConfig.forTesting(…);
Authenticator<BasicCredentials, Identity> authenticator =
credentials -> getAuthorizedIdentity();
resourceConfig.register(AuthFactory.binder(
new BasicAuthFactory<>(authenticator, "TEST", Identity.class)));
The effect is that I get 401 on all secured resources. Debugging shows that my authenticate function is never called at all! If I remove the registration, then I will not get 401s anymore (so the registration is not without effect), but now the secured resources are unsecured and get null for the #Auth annotated parameter.
So, how do I add the authenticator to the test context so it will work? The production code works fine with almost the same line.
This issue was fixed recently.
See https://github.com/dropwizard/dropwizard/pull/966
I just had the same problem, after looking at the tests in the dropwizard-auth project I came to this solution, hope it helps.
public class ExamplesResourceTest extends JerseyTest
{
#Override
protected TestContainerFactory getTestContainerFactory()
throws TestContainerException {
return new GrizzlyWebTestContainerFactory();
}
#Override
protected DeploymentContext configureDeployment() {
return ServletDeploymentContext.builder(new ExampleTestResourceConfig())
.initParam( ServletProperties.JAXRS_APPLICATION_CLASS, ExampleTestResourceConfig.class.getName() )
.initParam( ServerProperties.PROVIDER_CLASSNAMES, ExampleResource.class.getName() )
.build();
}
public static class ExampleTestResourceConfig extends DropwizardResourceConfig {
private ObjectMapper mapper = Jackson.newObjectMapper();
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public ExampleTestResourceConfig() {
super(true, new MetricRegistry());
register( new JacksonMessageBodyProvider( mapper, validator ) );
register( AuthFactory.binder( new BasicAuthFactory<>( new ExampleAuthenticator(), "realm", String.class ) ) );
}
}
#Test
public void exampleTest() throws AuthenticationException
{
Response response = target( "/api/example" )
.request( MediaType.APPLICATION_JSON )
.header( HttpHeaders.AUTHORIZATION, "Basic QmlsYm86VGhlU2hpcmU=" )
.get();
assertThat( response.getStatus() ).isEqualTo( 200 );
}
}
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'm currently trying to get a unit test set up for an HttpServlet class I have in Java. However, the Jetty documentation is kind of lacking and I'm a little stuck. I'm fairly certain the contextPath is /hbc as I printed it out using getContextPath() in the servlet. However, I'm not certain what a) the second parameter to the addServlet() method should be and b) what the URI should be.
The status code keeps returning back as 400 and the content is null. I'm not sure if it's because i'm not pointing to the right location (but I would think that would lead to a 404) or if something else is missing.
The servlet has an init(), processRequest(), doGet(), and doPost() method.
Thoughts?
public class HBCUnitTests extends TestCase {
private ServletTester tester;
#BeforeClass
public void setUp() throws Exception {
tester = new ServletTester();
tester.setContextPath("/hbc");
tester.addServlet(HubCommServlet.class, "/");
tester.start();
}
#AfterClass
public void tearDown() throws Exception {
tester.stop();
}
#Test
public void test() throws Exception {
HttpTester request = new HttpTester();
request.setMethod("POST");
request.setVersion("HTTP/1.1");
request.setURI("/");
System.out.println(request.generate());
HttpTester response = new HttpTester();
response.parse(tester.getResponses(request.generate()));
System.out.println(response.getContent());
System.out.println(response.getURI());
System.out.println(response.getReason());
assertEquals(200,response.getStatus());
assertEquals("<h1>Hello Servlet</h1>",response.getContent());
}
}
It looks like the second argument to addServlet() is the servlet-mapping.
If the contextPath is /hbc and your servlet is mapped to / then I would expect that you need to request /hbc/:
HttpTester request = new HttpTester();
...
request.setURI("/hbc/");
I ended up using JMock to test the servlet.