Stuart Watt
Stuart Watt

Reputation: 5391

Restlet: can't locate static files through Directory

Serving static files through Restlet is proving harder than it feels like it ought to be. I suspect some deep issue to do with context initialization, but I could be wrong. Basically, I have yet to be able to get any actual content served through a Directory although all the routing and all classic restlets are working just fine.

The core problem looks like it shows in log messages:

WARNING: No client dispatcher is available on the context. Can't get the target URI: war:///

I've tried a bunch of different base URLs, and none of them seem to make a difference. And when I debug, sure enough getClientDispatcher() is returning null.

When I modified the Spring initialization (wrongly) to use the original context rather than a child context, I didn't get this warning, and directory listings showed, but it also displayed about seven SEVERE messages about that being a security risk.

My Spring XML looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
                    http://www.springframework.org/schema/beans/spring-beans.xsd
                    http://www.springframework.org/schema/util
                    http://www.springframework.org/schema/util/spring-util.xsd">

<bean id="trackerComponent" class="org.restlet.ext.spring.SpringComponent">
    <property name="defaultTarget" ref="trackerApplication" />
</bean>

<bean id="trackerComponentChildContext" class="org.restlet.Context"> 
    <lookup-method name="createChildContext" bean="trackerComponent.context" /> 
</bean>

<bean id="trackerApplication" class="ca.uhnresearch.pughlab.tracker.application.TrackerApplication">
    <property name="root" ref="router" />
</bean>

<!-- Define the router -->
<bean name="router" class="org.restlet.ext.spring.SpringRouter">
    <constructor-arg ref="trackerComponentChildContext" />
    <property name="attachments">
        <map>
            <entry key="/" value-ref="staticsDirectory" />
        </map>
    </property>
</bean>

<bean id="staticsDirectory" class="ca.uhnresearch.pughlab.tracker.resource.SpringDirectory">
    <constructor-arg ref="router" />
    <constructor-arg value="war:///" />
    <property name="listingAllowed" value="true" />
</bean>

...

My class SpringDirectory is just a wrapper to provide a Context from a parent Router, like the other Spring helpers, and it seems to work just fine: the same context is passed along -- and none of the child contexts have a getClientDispatcher() that works.

In case it's needed, here's some of the relevant sections of web.xml. I'm not entirely sure why I needed to add the FILE to allow client access (I want to serve through the web container after all) but I did try with a file: URL too, and that didn't make any difference that I could see, except that I had to add extra settings in unusual places.

<servlet>
    <servlet-name>tracker</servlet-name>
    <servlet-class>org.restlet.ext.spring.SpringServerServlet</servlet-class>
    <init-param>
        <param-name>org.restlet.clients</param-name>
        <param-value>HTTP HTTPS FILE</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>tracker</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

I'd be really grateful for advice on this -- I've actually spent more time trying to get an index.html displayed than I have on all the Restlets combined, so any thoughts or pointers will be very welcome.

UPDATE

Thierry's response has been helpful, but I still have some issues and still can't serve static files. The only variation from this is that I don't have a test.CustomSpringServerServlet -- and it's not entirely clear I need one, as the additional init-param seems to be accepted by the default.

When running with a Jetty JAR file (mvn jetty:run-war) I now get a stack backtrace:

WARNING: Exception or error caught in status service
java.lang.NoSuchMethodError: org.restlet.Context.getClientDispatcher()Lorg/restlet/Restlet;
at ca.uhnresearch.pughlab.tracker.restlets.DirectoryFactoryBean$1.getContext(DirectoryFactoryBean.java:16)
at org.restlet.Restlet.handle(Restlet.java:220)
at org.restlet.resource.Finder.handle(Finder.java:449)
at org.restlet.resource.Directory.handle(Directory.java:245)
at org.restlet.routing.Filter.doHandle(Filter.java:156)
...

This stack backtrace could easily be a consequence of my updates -- I'm now using Restlets 2.3.1 and Spring 3.1.4, as it's very similar to the previous warning.

Strangely enough, I don't get this from mvn jetty:run, which paradoxically is now serving directory listings at the top level, but still refuses to serve any files. So that confirms the WAR protocol is able to access something. The war:// protocol isn't highly documented so I'm kind of guessing it's just going to serve from the war contents, and it certainly isn't yet able to do that.

I'd still like the file:// protocol to work -- to be honest, any serving of any static files would be fantastic, I am getting beyond caring. However, trying that still results in errors:

WARNING: The protocol used by this request is not declared in the list of client connectors. (FILE). In case you are using an instance of the Component class, check its "clients" property.

Which I don't really understand, as it is in the clients list in the web.xml and there's nowhere else that makes sense I can put it.

This is proving hard enough that I feel maybe I should throw together a Github repo for people to play with.

Upvotes: 0

Views: 465

Answers (1)

Thierry Templier
Thierry Templier

Reputation: 202138

Edit: I completely updated my answer to describe how to fix your problem

I investigated more deeper you problem and I can reproduce it. In fact, it's a problem when configuring the directory itself in Spring. In fact, it's located at the context which is responsible to bring the client dispatcher.

Here are more details. In fact, the client protocol WAR is automatically registered when the Restlet component is created within the default or Spring servlet. See the method ServerServlet#init(Component). So the corresponding client dispatcher is set within the context of the component.

So you need to pass this context in the constructor of the Directory when instantiating it. I reworked the Spring configuration to show how to fix your problem:

public class DirectoryFactoryBean implements FactoryBean<Directory> {
    private Component component;

    @Override
    public Directory getObject() throws Exception {
        Directory directory = new Directory(component.getContext(), "war:///") {
            @Override
            public Context getContext() {
                // Display if the client dispatcher is correctly set!
                System.out.println(">> getContext().getClientDispatcher() = "+super.getContext().getClientDispatcher());

                return super.getContext();
            }
        };
        directory.setListingAllowed(true);
        return directory;
    }

    @Override
    public Class<?> getObjectType() {
        return Directory.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public Component getComponent() {
        return component;
    }

    public void setComponent(Component component) {
        this.component = component;
    }
}

Here is the content of the class ``:

public class CustomSpringServerServlet extends SpringServerServlet {
    @Override
    protected void init(Application application) {
        super.init(application);
    }

    @Override
    protected void init(Component component) {
        super.init(component);

        ClientList list = component.getClients();
        for (Client client : list) {
            System.out.println(">> client = "+client);
        }
    }
}

Here is the configuration in the web.xml file:

<web-app>
    <context-param>
        <param-name>org.restlet.application</param-name>
        <param-value>org.restlet.tutorial.MyApplication</param-value>
    </context-param>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>tracker</servlet-name>
        <servlet-class>test.CustomSpringServerServlet</servlet-class>
        <init-param>
            <param-name>org.restlet.component</param-name>
            <param-value>myComponent</param-value>
        </init-param>
        <init-param>
            <param-name>org.restlet.clients</param-name>
            <param-value>HTTP HTTPS FILE</param-value>
        </init-param>
    </servlet>
</web-app>

Here is the corresponding Spring configuration:

<bean id="myComponent" class="org.restlet.ext.spring.SpringComponent">
    <property name="defaultTarget" ref="router" />
</bean>

<bean name="router" class="org.restlet.ext.spring.SpringRouter">
    <property name="attachments">
        <map>
            <entry key="/dir" value-ref="staticsDirectory" />
        </map>
    </property>
</bean>

<bean id="staticsDirectory" class="test.DirectoryFactoryBean">
    <property name="component" ref="myComponent" />
</bean>

Hope it helps, Thierry

Upvotes: 1

Related Questions