Restlet URI Pattern - java

I work on 2.1-M7 version of Restlet (I have to update it but this is an another problem)
I use directly Restlet, without any webserver before it Starting a component. Adding some virtualhosts on it.
And in the host I add entrypoints with method attach(string uriPattern, Restlet entrypoint)
My problem is :
When I add with attach the uri "/test" with the entrypoint Test.class (with a method who print : "hello world") with a curl I can call "/testmeagain" and it's work (return "hello world") because it's a pattern?
So I use this : http://docs.oracle.com/javase/1.5.0/docs/api/java/util/regex/Pattern.html?is-external=true
And try "/test$" but in curl "/test" and "/testmeagain" return 404 now
Maybe I miss something?
Thank you if you have any suggestion or response to help me.

In fact, in Restlet, there is a matching mode for routes. Here is the behavior in the framework:
When you attach a route on a virtual, the default mode is "STARTS WITH". So with something like attach("/test", ...), URLs like /test and /testsomething will match.
When you attach a route on a router, the default mode is "EQUALS". So with something like attach("/test", ...), only URL /test will match.
The attach method returns a template route on which you can change this matching:
TemplateRoute route = component.getDefaultHost().attach(
"/test", new Restlet() {
#Override
public void handle(Request request, Response response) {
response.setEntity("test", MediaType.TEXT_PLAIN);
}
});
// Default matching mode
int defaultMatching = route.getMatchingMode();
// Set another matching mode
route.setMatchingMode(Template.MODE_EQUALS);
In fact, it's more usually to implement a Restlet application and attach it to a virtual host on the component. In this case, you will have exact matching.
Here is the way to do:
Component component = new Component();
(...)
MyRestletApplication application = new MyRestletApplication();
component.getDefaultHost().attachDefault(application);
Here is a sample content for the application:
public class MyRestletApplication extends Application {
#Override
public Restlet createInboundRoot() {
Router router = new Router(getContext());
TemplateRoute route = router.attach("/test", MyServerResource.class);
// Default matching mode
int defaultMatching = route.getMatchingMode();
return router;
}
}
And the content of the server resource:
public class MyServerResource extends ServerResource {
#Get
public String test() throws Exception {
return "test";
}
}
Hope it helps you,
Thierry

Related

Running SpingBoot app on multiple ports with different controllers

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

Passing WebSocket parameters to a programmatical endpoint

My front-end code looks like this:
const ws = new WebSocket("wss://localhost/yeah?param1=value1&param2=value2");
My endpoint at the backend is programmatical:
class YeahEndpoint extends Endpoint {
...
#Override
public void onOpen(Session session, EndpointConfig ec) {
Map<String, String> params = session.getPathParameters(); <-- returns empty map! No param1 or param2.
}
...
}
As I was not able to find relevant information on the web, I need to ask this here: how a programmatical endpoint obtains the request parameters?
I simply should have used session.getQueryString() instead of session.getPathParameters().
If you are using the Jetty as the back-end, look at the following code:
session.getUpgradeRequest().getParameter("param")

Wiremock proxy record feature - can it replace Host or IP in responses?

I am recording rest api's with wiremock... in my case for SharePoint.
So I set up a recorder:
java -jar wiremock-standalone-2.18.0.jar
Now I go to http://localhost:8080/__admin/recorder/ and I enable recording for my http://sharepointhost.
Now I make some requests to sharepoint rest apis through http://localhost:8080.
But the rest api responses still reference the http://sharepointhost.
Is there a way to turn on some sort of reverse proxy or URL pattern string replace so I can avoid this issue? What is the way to do that in my case? Do I need to use the Java variety of the recorder instead of using the standalone?
WireMock supports "Extensions." And there are some pre-packaged extension types called "Transformers."
There is an extension type that allows you to intercept responses of http requests. Here you can then replace contents of responses.
See http://wiremock.org/docs/extending-wiremock/
I created a GitHub repository with a response body URL rewrite extension:
https://github.com/nddipiazza/wiremock-response-body-url-rewriter
public class ResponseBodyUrlRewriteTransformer extends ResponseTransformer {
final int wiremockPort;
final String wiremockBindAddress;
final private List<String> urlsToReplace;
public ResponseBodyUrlRewriteTransformer(String wiremockBindAddress, int wiremockPort, List<String> urlsToReplace) {
this.urlsToReplace = urlsToReplace;
this.wiremockBindAddress = wiremockBindAddress;
this.wiremockPort = wiremockPort;
}
private String replaceUrlsInBody(String bodyText) {
for (String urlToReplace : urlsToReplace) {
bodyText = bodyText.replaceAll(Pattern.quote(urlToReplace),
"http://" + wiremockBindAddress + ":" + wiremockPort);
}
return bodyText;
}
#Override
public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
if (response.getStatus() == 200) {
ContentTypeHeader contentTypeHeader = response.getHeaders().getContentTypeHeader();
if (contentTypeHeader != null && contentTypeHeader.mimeTypePart().contains("xml")) {
return Response.response()
.body(replaceUrlsInBody(response.getBodyAsString()))
.headers(response.getHeaders())
.status(response.getStatus())
.statusMessage(response.getStatusMessage())
.fault(response.getFault())
.chunkedDribbleDelay(response.getChunkedDribbleDelay())
.fromProxy(response.isFromProxy())
.build();
}
}
return response;
}
#Override
public String getName() {
return "ResponseBodyUrlRewriteTransformer";
}
}
Yes. You can launch WireMock as a proxy with automatic record mode. The command you need is this:
java -jar wiremock-standalone-2.18.0.jar --port 8787 --print-all-network-traffic --verbose --enable-browser-proxying --record-mappings
The important params there are enable-browser-proxying and record-mappings
The proxy is running on port 8787 and you have to configure your browser to use proxy localhost:8787
Now you can browse any web site, and all the trafic will be recorded.

Vertx - missing GET parameters while securing route using OAuth2

I followed this example of securing route using OAuth2 with GitHub provider: http://vertx.io/docs/vertx-web/java/#_oauth2authhandler_handler and it works fine, except missing GET parameters after request redirection.
My code:
public class MyVerticle extends AbstractVerticle {
#Override
public void start() throws Exception {
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
OAuth2Auth authProviderGitHub = GithubAuth.create(vertx, "<CLIENT_ID>", "<CLIENT_SECRET>");
OAuth2AuthHandler oauth2 = OAuth2AuthHandler.create(authProviderGitHub, "http://localhost:8080/callback");
oauth2.setupCallback(router.route());
router.route("/protected/*").handler(oauth2);
Handler<RoutingContext> requestHandler = (routingContext) -> {
String paramValue = routingContext.request().getParam("param");
routingContext.response().end("PARAM: " + paramValue);
};
router.get("/endpoint").handler(requestHandler);
router.get("/protected/endpoint").handler(requestHandler);
server.requestHandler(router::accept).listen(8080);
}
}
I have two simple endpoints:
/endpoint // public, without protection
and
/protected/endpoint // protected with OAuth2
When I call from a browser /endpoint with
http://localhost:8080/endpoint?param=foo
it works as expected and return PARAM: foo, whereas when I call protected endpoint with
http://localhost:8080/protected/endpoint?param=foo
it correctly redirect me to GitHub login page, then return query to my handler but without GET parameters, so response from the endpoint is PARAM: null.
Any idea what I'm doing wrong?
On vert.x <= 3.4.2 only the path was being used for the redirect, the 3.5 series has been improved and can rely on the full uri, so your code will work on that version.

Trace SOAP request/responses with JAX-WS on the client side

I'm using JAX-WS reference implementation (2.1.7) and I want to trace SOAP request/responses on the client side. Actually, what I need is to examine some Http headers when I receive the response.
Following these previous questions ( Tracing XML request/responses with JAX-WS and Java JAX-WS web-service client: how log request & response xml? ), I've created my own handler to log when I send a request and receive a response:
public class SHandler implements SOAPHandler<SOAPMessageContext>
{
private static final Logger log = Logger.getLogger(SHandler.class);
#Nullable
#Override
public Set<QName> getHeaders()
{
log.debug(">>>>>>>>>>> GetHeaders");
return null;
}
#Override
public boolean handleMessage(SOAPMessageContext soapMessageContext)
{
log.debug(">>>>>>>>>>> HandleMessage");
return true;
}
#Override
public boolean handleFault(SOAPMessageContext soapMessageContext)
{
log.debug(">>>>>>>>>>> HandleFault");
return true;
}
#Override
public void close(MessageContext messageContext)
{
log.debug(">>>>>>>>>>> Close");
}
}
and I add the handler to the handler chain during the service initialisation:
#WebServiceClient(name = "MyService", targetNamespace = "http://www.whatever.com/", wsdlLocation = "file:/path/to/wsdl")
public class MyService extends Service
{
public MyService(URL wsdlLocation) {
super(...);
initializeBinding();
}
#WebEndpoint(name = "MyOperation")
public MyPort getMyPort() {
return super.getPort(new QName("http://www.whatever.com/", "MyPort"), MyPort.class);
}
private void initializeBinding() {
MyPort port = getMyPort();
BindingProvider bindingProvider = ((BindingProvider) port);
List handlerChain = bindingProvider.getBinding().getHandlerChain();
handlerChain.add(new SHandler());
bindingProvider.getBinding().setHandlerChain(handlerChain);
}
...
}
The problem is that this doesn't work at all on the client side. I don't see any logs and my handler is never executed when I send a request and receive a response.
Notice that there is no specific WSDL related to this issue because I work on an MDA platform that generates client/server artifacts from any WSDL. In addition, I cannot do this at configuration level as all is generated, so I can only do it programmatically (I've been googling this and all the solutions that I find are either the one in the original post or using the handler-chain.xml configuration file).
Am I missing something? Is there any other way of doing this?
Thanks in advance.
If you only want to look at the SOAP messages run with
-Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true
VM argument.
Why not use #HandlerChain(file = "....") annotation?
From my pov, you can not mix constructor- and annotation-based configurations as on-deploy webservice initialization and creating new instance of your service class are performed in absolutely different contexts.
there are 2 tools that you can use to help with this:
soapui
Eclipse tcp/ip monitor
Both tools offer a proxy mode, which intercepts, logs and forwards requests and responses.

Categories

Resources