[PAX WEB] Url Pattern is auto-generated from Alias and how to register exact url pattern

51 views
Skip to first unread message

Miroslav Beranič

unread,
Feb 6, 2020, 6:14:14 AM2/6/20
to OPS4J
Hi all,

I work with Pax Web on Karaf 4.3.0. I am trying to register Servlet with exact url, but I see url pattern is auto-generated from alias.
I look at the current master branch - version 8.0.0-SNAPSHOT. Class:

org.ops4j.pax.web.service.spi.model.ServletModel


constructor:
#ServletModel(org.ops4j.pax.web.service.spi.model.ContextModel, javax.servlet.Servlet, java.lang.String, java.lang.String[], java.lang.String, java.util.Dictionary<java.lang.String,?>, java.lang.Integer, java.lang.Boolean, javax.servlet.MultipartConfigElement)


it calles method ( private static )
new String[]{aliasAsUrlPattern(alias)}


    private static String aliasAsUrlPattern(final String alias) {
        String urlPattern = alias;
        if (urlPattern != null && !urlPattern.equals("/")
                && !urlPattern.contains("*")) {
            if (urlPattern.endsWith("/")) {
                urlPattern = urlPattern + "*";
            } else {
                urlPattern = urlPattern + "/*";
            }
        }
        return urlPattern;
    }


so it always creates a url pattern, as I guess the name suggest, but why? I would like to register exact URL, not the pattern. As it looks now, this is not possible to do - as there is no argument to pass in to control the flow.

As it looks from the git history/log this is quite "old" code - from 2008 - 2013, so I guess this is not new and I guess everybody are ok with this? So in this case, what is the usecase for it? As for my usecase - this is not the required behavior.

For example, Servlet, registered with /hello, also matches /hello/1, but I want /hello/1 to return HTTP 404.


So for now, only really question is - is this expected behavior or is there a room to change this? If so, how can one, with existing solution, register Servlet with exact URL?

Exact strack of calls looks like this:

<init>:53, ServletModel (org.ops4j.pax.web.service.spi.model)
registerServlet:224, HttpServiceStarted (org.ops4j.pax.web.service.internal)
registerServlet:210, HttpServiceStarted (org.ops4j.pax.web.service.internal)
registerServlet:69, HttpServiceProxy (org.ops4j.pax.web.service.internal)
register:97, ServletWebElement (org.ops4j.pax.web.extender.whiteboard.internal.element)
registerWebElement:392, WebApplication (org.ops4j.pax.web.extender.whiteboard.internal)
addWebElement:188, WebApplication (org.ops4j.pax.web.extender.whiteboard.internal)
addingService:193, AbstractTracker (org.ops4j.pax.web.extender.whiteboard.internal.tracker)
addingService:46, AbstractTracker (org.ops4j.pax.web.extender.whiteboard.internal.tracker)
customizerAdding:941, ServiceTracker$Tracked (org.osgi.util.tracker)
customizerAdding:870, ServiceTracker$Tracked (org.osgi.util.tracker)
trackAdding:256, AbstractTracked (org.osgi.util.tracker)
track:229, AbstractTracked (org.osgi.util.tracker)
serviceChanged:901, ServiceTracker$Tracked (org.osgi.util.tracker)
invokeServiceListenerCallback:990, EventDispatcher (org.apache.felix.framework)
fireEventImmediately:838, EventDispatcher (org.apache.felix.framework)
fireServiceEvent:545, EventDispatcher (org.apache.felix.framework)
fireServiceEvent:4595, Felix (org.apache.felix.framework)
registerService:3587, Felix (org.apache.felix.framework)
registerService:348, BundleContextImpl (org.apache.felix.framework)
registerService:355, BundleContextImpl (org.apache.felix.framework)

I've changed this in my branch, added additional method with same name and initParams:

private static String aliasAsUrlPattern(final String alias, final Dictionary<String, ?> initParams) {
   final Object exactUrlPatternFromAliasParam = initParams.get("exactUrlPattern");
   Boolean exactUrlPatternFromAlias = Boolean.FALSE;
   if (null != exactUrlPatternFromAliasParam) {
      if (exactUrlPatternFromAliasParam instanceof String) {
         final String flag = ((String) exactUrlPatternFromAliasParam).toLowerCase();
         exactUrlPatternFromAlias = Boolean.parseBoolean(flag);
       } else if(exactUrlPatternFromAliasParam instanceof Boolean) {
         final Boolean flag = (Boolean) exactUrlPatternFromAliasParam;
         exactUrlPatternFromAlias = flag;
      } else if (exactUrlPatternFromAliasParam instanceof Serializable) {
         final String flag = exactUrlPatternFromAliasParam.toString().toLowerCase();
         exactUrlPatternFromAlias = Boolean.parseBoolean(flag);
       }
   }
   final String result;
   if (Boolean.TRUE.equals(exactUrlPatternFromAlias)) {
      // Break the reference
      result = alias.toString();
   } else {
      result = aliasAsUrlPattern(alias);
   }
   return result;
}


This calls original unmodified version of method aliasAsUrlPattern.

Caller has to pass additional init parameter "exactUrlPattern". I usually call it with Boolean.TRUE. I guess flag could/should be added into org.ops4j.pax.web.extender.whiteboard.ExtenderConstants.

To note one other thing -- this is " one of the use cases ", I as see there is also construction, that allows to pass in defined url patterns, but I did not find how to call it. The call I am using is:

props.put(ExtenderConstants.DEFAULT_INIT_PREFIX_PROP + "exactUrlPattern", Boolean.TRUE);
bundleContext.registerService(Servlet.class, new WhiteboardServlet("/hello"), props);

Could be, that if HttpService interface is used, this is not the case.

I've tried to solve this using

props.put(ExtenderConstants.PROPERTY_URL_PATTERNS, "/hello");

This is illegal state as implemented by the constructor of class:

org.ops4j.pax.web.extender.whiteboard.internal.element.ServletWebElement

if (alias != null && urlPatterns != null && urlPatterns.length != 0) {
    LOG.warn("Registered servlet [{}] cannot have both alias and url patterns.", getServiceID());
    valid = false;
}


I did not look into this yet, but why is this implemented like so? If this would be changed, it would make use of existing constants.


Kind Regards,
Miroslav


Grzegorz Grzybek

unread,
Feb 6, 2020, 6:48:49 AM2/6/20
to op...@googlegroups.com
Hello

Yes - this is all confusing... I'm just in the process of reviewing master branch (pax web 8.0.0-SNAPSHOT) to check R7 (and even R6) compliance.... There's really lot to do still...

Indeed - I checked org.ops4j.pax.web.service.spi.model.ServletModel#aliasAsUrlPattern() and it really changes alias like "/something" into "/something/*", but Http Service specification explicitly says (102.2 Registering Servlets):

For example, if the Http Service implementation is listening to port 80 on the machine www.acme.com and the Servlet object is registered with the name "/servlet" [...]

I noticed that the spec is not clear about "name" vs. "alias" vs. "URI pattern"... And I believe your interpretation is the correct one - "alias" is "exact URI pattern"... Of course it still doesn't tell anything about actual context to use for servlet registration (I'm just working on making it less confusing in pax web 8).

So the only recommendation I have for you is not to use direct registration (httpService.registerServlet()), but to use whiteboard approach and register Servlet.class OSGi service using "BundleContext.registerService(Servlet.class, servlet, props)" where properties include either:

props.put("urlPatterns", new String[] { "/hello" }); // old Pax Web approach

or:

props.put("osgi.http.whiteboard.servlet.pattern", new String[] { "/hello" }); // new R6+ Whiteboard approach (chapter 140.4 "Registering Servlets").

I hope this helps and explains the confusion.

regards
Grzegorz Grzybek

--
--
------------------
OPS4J - http://www.ops4j.org - op...@googlegroups.com

---
You received this message because you are subscribed to the Google Groups "OPS4J" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ops4j+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ops4j/179c33ab-fdd3-451f-b4ff-57f2fc934a04%40googlegroups.com.

Jean-Baptiste Onofré

unread,
Feb 6, 2020, 6:50:17 AM2/6/20
to OPS4J
Hi

Can you please share how you register the servlet ? Via the httpService.register, Servlet service registration or whiteboard ?

Thanks,
Regards
JB

Jean-Baptiste Onofré

unread,
Feb 6, 2020, 6:50:57 AM2/6/20
to OPS4J
Hi Grzegorz,

Let's sync together about action plan as I started some changes as well.

Regards
JB

Grzegorz Grzybek

unread,
Feb 6, 2020, 7:00:53 AM2/6/20
to op...@googlegroups.com
Hello

czw., 6 lut 2020 o 12:50 Jean-Baptiste Onofré <jeanbapti...@gmail.com> napisał(a):
Hi Grzegorz,

Let's sync together about action plan as I started some changes as well.

Sure - I started a thread named "Pax Web 8 and beyond" ;) And I'm on IRC as always.

regards
Grzegorz

Miroslav Beranič

unread,
Feb 6, 2020, 7:01:20 AM2/6/20
to OPS4J
Hi Grzegorz,

I am using bundleContext "BundleContext.registerService(Servlet.class, servlet, props)", not the httpService ( httpService.registerServlet() )

I've tried props.put("urlPatterns", new String[] { "/hello" }); // old Pax Web approach , as it was one of my first tries to make it work, but ... only properties with "init." prefix are passed on to the creation of Element. But with that, Exception is thrown, saying that urlPattern and alias can not exist at the same time.

I did not try the props.put("osgi.http.whiteboard.servlet.pattern", new String[] { "/hello" }); // new R6+ Whiteboard approach (chapter 140.4 "Registering Servlets"), but did not see in the code, where this is picked up. Will try this now.

So, I see JB is also asking .. I use OSGi Service registration of Servlet interface and implementation.

Kind Regards,
Miroslav



Dne četrtek, 06. februar 2020 12.48.49 UTC+1 je oseba Grzegorz Grzybek napisala:
Hello

Yes - this is all confusing... I'm just in the process of reviewing master branch (pax web 8.0.0-SNAPSHOT) to check R7 (and even R6) compliance.... There's really lot to do still...

Indeed - I checked org.ops4j.pax.web.service.spi.model.ServletModel#aliasAsUrlPattern() and it really changes alias like "/something" into "/something/*", but Http Service specification explicitly says (102.2 Registering Servlets):

For example, if the Http Service implementation is listening to port 80 on the machine www.acme.com and the Servlet object is registered with the name "/servlet" [...]

I noticed that the spec is not clear about "name" vs. "alias" vs. "URI pattern"... And I believe your interpretation is the correct one - "alias" is "exact URI pattern"... Of course it still doesn't tell anything about actual context to use for servlet registration (I'm just working on making it less confusing in pax web 8).

So the only recommendation I have for you is not to use direct registration (httpService.registerServlet()), but to use whiteboard approach and register Servlet.class OSGi service using "BundleContext.registerService(Servlet.class, servlet, props)" where properties include either:

props.put("urlPatterns", new String[] { "/hello" }); // old Pax Web approach

or:

props.put("osgi.http.whiteboard.servlet.pattern", new String[] { "/hello" }); // new R6+ Whiteboard approach (chapter 140.4 "Registering Servlets").

I hope this helps and explains the confusion.

regards
Grzegorz Grzybek

To unsubscribe from this group and stop receiving emails from it, send an email to op...@googlegroups.com.

Miroslav Beranič

unread,
Feb 6, 2020, 7:07:01 AM2/6/20
to OPS4J
Hi JB,

Took the code from:
https://github.com/ops4j/org.ops4j.pax.web/tree/master/samples/whiteboard

to be able to use ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID => Virtual hosts "feature" of Jetty.

{
    Dictionary<String, Object> props;
    props = new Hashtable<>();

    props.put(ExtenderConstants.PROPERTY_ALIAS, "/hello");
    props.put(ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID, "virtualhost");

    // Fail with Exception: from inside constructor of class org.ops4j.pax.web.extender.whiteboard.internal.element.ServletWebElement
    // props.put(ExtenderConstants.PROPERTY_URL_PATTERNS, "/hello");


    props.put(ExtenderConstants.DEFAULT_INIT_PREFIX_PROP + "exactUrlPattern", Boolean.TRUE);

    servletReg = bundleContext.registerService(Servlet.class, new WhiteboardServlet("/hello"), props);
}

Kind Regards,
Miroslav


Dne četrtek, 06. februar 2020 12.50.17 UTC+1 je oseba Jean-Baptiste Onofré napisala:
Hi

Can you please share how you register the servlet ? Via the httpService.register, Servlet service registration or whiteboard ?

Thanks,
Regards
JB

To unsubscribe from this group and stop receiving emails from it, send an email to op...@googlegroups.com.

Miroslav Beranič

unread,
Feb 6, 2020, 7:26:12 AM2/6/20
to OPS4J
Hi Grzegorz,

I can "confirm" now,
either:

props.put("urlPatterns", new String[] { "/hello" }); // old Pax Web approach

or:

props.put("osgi.http.whiteboard.servlet.pattern", new String[] { "/hello" }); // new R6+ Whiteboard approach (chapter 140.4 "Registering Servlets").

will not work. Servlet is not registered. As constructor of class org.ops4j.pax.web.extender.whiteboard.internal.element.ServletWebElement will mark Servlet as valid = false, and warning log is written. Code is:
if (alias != null && urlPatterns != null && urlPatterns.length != 0) {
     LOG.warn("Registered servlet [{}] cannot have both alias and url patterns.", getServiceID());
    valid = false;
}

And somwhere down the pipeline, Servlet is not registered. I do not have exact location with me now.


Kind Regards,
Miroslav






Dne četrtek, 06. februar 2020 13.07.01 UTC+1 je oseba Miroslav Beranič napisala:

Grzegorz Grzybek

unread,
Feb 6, 2020, 7:27:03 AM2/6/20
to op...@googlegroups.com
Hello

I've checked org.ops4j.pax.web.extender.whiteboard.internal.tracker.ServletTracker#createWebElement() and indeed - it takes "alias" from "alias" property and may take patterns from "urlPatterns" or "osgi.http.whiteboard.servlet.pattern" - but you CAN'T specify "alias" property to do it.

Please don't use:

props.put(ExtenderConstants.PROPERTY_ALIAS, "/hello");

regards
Grzegorz

To unsubscribe from this group and stop receiving emails from it, send an email to ops4j+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ops4j/9016e27b-4059-4c2d-83ad-1ae42d5083c5%40googlegroups.com.

Miroslav Beranič

unread,
Feb 6, 2020, 7:38:18 AM2/6/20
to op...@googlegroups.com

V V čet., 6. feb. 2020 ob 13:27 je oseba Grzegorz Grzybek <gr.gr...@gmail.com> napisala:


--

Miroslav Beranič

unread,
Feb 6, 2020, 7:44:27 AM2/6/20
to op...@googlegroups.com
Hi Grzegorz,

so yes, this was it.

It can be used:

props.put(ExtenderConstants.PROPERTY_URL_PATTERNS, new String[]{"/sl/zdravo", "/en/hello", "/de/halo"});

or

props.put(ExtenderConstants.PROPERTY_ALIAS, "/sl/zdravo");

but can not use both. But, now Alias is missing in the Karaf http:list and some other internal logic, dependant on the alias is also not working, but ... I guess I can work around it.

OK, this kind of solved the problem for now. But why it is not allowed to have both? I guess you would want to have alias and n other url patterns for the Servlet, or not?


Kind regards,
Miroslav



V V čet., 6. feb. 2020 ob 13:38 je oseba Miroslav Beranič <miroslav...@mibesis.si> napisala:

Grzegorz Grzybek

unread,
Feb 6, 2020, 7:50:16 AM2/6/20
to op...@googlegroups.com
In Pax Web 8 I'll try to make it clear "what is alias"?

I think it comes from (correct me if I'm wrong) ... https://httpd.apache.org/docs/2.4/mod/mod_alias.html#alias meaning a "place in URI namespace accessible via HTTP". Http Service spec (chapter 102 of OSGi CMPN) confuses "name", "alias" and "URI pattern" heavily...

When I was fixing some bugs in Pax Web some time ago, I found that alias SHOULD BE just specialized, single-element array of URI patterns. It helped with the tests and general comprehension, but I didn't notice this aliasAsUrlPattern() method...

regards
Grzegorz

Miroslav Beranič

unread,
Feb 6, 2020, 7:58:11 AM2/6/20
to op...@googlegroups.com
Hi,

I am not sure any more ... what it should be used for :) At first I understood it as a URL, but now, when I was looking at the PAX Web code ... not quite sure what it is used for.

But either way, as you said, all these different names make in quite un-clear what they are used for.

Thank you for the support and clearing the fog.

Kind Regards,
Miroslav



V V čet., 6. feb. 2020 ob 13:50 je oseba Grzegorz Grzybek <gr.gr...@gmail.com> napisala:

Grzegorz Grzybek

unread,
Feb 15, 2020, 11:31:06 AM2/15/20
to op...@googlegroups.com
Hello

I've finally found the answer to your question:

For example, Servlet, registered with /hello, also matches /hello/1, but I want /hello/1 to return HTTP 404.

I've read the 102nd chapter of OSGi CMPN Http Service spec and I found:

102.4 Mapping HTTP Requests to Servlet and Resource Registrations

When an HTTP request comes in from a client, the Http Service checks to see if the requested URI matches any registered aliases. A URI matches only if the path part of the URI is exactly the same string. Matching is case sensitive.

Which initially made me think that you're correct. But check this out:

If it does match, a matching registration takes place, which is processed as follows:
[...]
6. If there is no match, the Http Service must attempt to match sub-strings of the requested URI to registered aliases. The sub-strings of the requested URI are selected by removing the last "/" and everything to the right of the last "/".

The Http Service must repeat this process until either a match is found or the sub-string is an empty string. [...]

So, because Pax Web doesn't do what Tomcat/Jetty/Undertow do perfectly (VHost/Context/Servlet mapping), I think that actually an "/alias" should always be changed to "/alias/*" to comply with specification.

If you want to do exact matching, just use the methods accepting URL parameters.


regards
Grzegorz Grzybek

czw., 6 lut 2020 o 12:14 Miroslav Beranič <miroslav...@mibesis.si> napisał(a):
--
--
------------------
OPS4J - http://www.ops4j.org - op...@googlegroups.com

---
You received this message because you are subscribed to the Google Groups "OPS4J" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ops4j+un...@googlegroups.com.

Jean-Baptiste Onofré

unread,
Feb 15, 2020, 3:45:38 PM2/15/20
to op...@googlegroups.com
Hi

According to the spec, I would agree. +1. 

Regards 
JB

Achim Nierbeck

unread,
Feb 16, 2020, 6:51:59 AM2/16/20
to op...@googlegroups.com
Agree 

--

Apache Member
Apache Karaf <http://karaf.apache.org/> Committer & PMC
OPS4J Pax Web <http://wiki.ops4j.org/display/paxweb/Pax+Web/> Committer & Project Lead
blog <http://notizblog.nierbeck.de/>
Co-Author of Apache Karaf Cookbook <http://bit.ly/1ps9rkS>

Miroslav Beranič

unread,
Feb 18, 2020, 11:19:41 AM2/18/20
to op...@googlegroups.com
Grzegorz,

thank you for this in depth explanation. I guess what is missing here is updated documentation? It sure looks like, this is by-the-spec implementation, but can be miss-used as I've demonstrated.

Kind Regards,
Miroslav
 

V V ned., 16. feb. 2020 ob 12:51 je oseba 'Achim Nierbeck' via OPS4J <op...@googlegroups.com> napisala:

Jean-Baptiste Onofré

unread,
Feb 18, 2020, 1:41:23 PM2/18/20
to op...@googlegroups.com
Hi

Yeah it’s just a documentation manner imho. 

Regards 
JB

Reply all
Reply to author
Forward
0 new messages