| HTTP Service specification explained by Example |
|
|
| Written by Valery Abu-Eid | |
| Saturday, 11 October 2008 22:24 | |
|
HTTP Service specification of the OSGi Compendium Services aims to provide developers with a standard way to run/provide Web Components in OSGi Environments - Currently only Servlets and Resources. Like any other OSGi Compendium Service, HTTP Service has a standard API and implementations provided by different vendors, in this article we will use the implementation provided by Pax Web project since it's compatible with the three major OSGi Frameworks. HTTP Service is the best solution for providing Servlets and Web Resources, but for Web Applications development it's always better to use OSGi-compatible Web Frameworks (like Wicket, Struts and Jasper) instead of working with a low level API.HttpService and HttpContext interfacesWorking with HTTP Service you will by using two interfaces: org.osgi.service.http.HttpService and org.osgi.service.http.HttpContext. The HttpService will be provided as an OSGi Service by the implementation of the HTTP Service specification, you will use this service to register Servlets and Resources (e.g., images, html files, java script files, etc.). When registering Servlets and resources you will associate them to an HttpContext, you can either provide the HttpContext yourself or use a default one provided by the HTTP Service implementation (which should be fine in most cases) - More on Http Context is covered below. The example application and its source code are contained in http-service-example.zip. The example application was tested with Equinox, Felix and Knopflerfish.Providing ServletsTo register a Servlet you need to:
When processing requests the HTTP Service will look for the exact match of the requested URI. If a servlet with such alias not found, it will look for the longest alias wich is a parent for the requested URI. Below are few examples: "/hello" is the exact match of "/hello" "/hello" matches "/" "/author/title" matches "/author" "/author/title" doesn't match "/auth" "/author/title" is a more suitable match than "/author" for "/author/title/page" The same servlet instance shouldn't be registered twice! When a servlet is registered its init(...) method will be invoked, so the same servlet instance (the same object not the the same type) shouldn't be registered so its init method wouldn't be called twice. Below are two example classes, the Activator which runs the registrar class and binds the HTTP Service to it and GreetingsServletRegistrar which registers the servlet.
import org.osgi.service.http.HttpService;
import ...;
public class Activator implements BundleActivator {
//@Override
public void start(BundleContext bundleContext) throws Exception {
/// OsgiServiceBinder is a utility class that binds OSGi Services
/// to properties using setter methods. We will use it here to
/// bind the HttpService to the GreetingsServletRegistrar.
binder = new OsgiServiceBinder(bundleContext);
binder.bind(getGreetingsServletRegistrar(), "setHttpService",
ServiceFilter.forInterface(HttpService.class.getName()));
}
private final GreetingsServletRegistrar greetingsServletRegistrar =
new GreetingsServletRegistrar();
protected GreetingsServletRegistrar getGreetingsServletRegistrar() {
return greetingsServletRegistrar;
}
private OsgiServiceBinder binder;
// ...
}
import org.osgi.service.http.HttpService;
public class GreetingsServletRegistrar {
/**
* This method is invoked when the HTTP Service is updated.
*/
protected void httpServiceUpdated() {
if (getHttpService() != null) {
registerGreetingsServlet();
}
}
protected void registerGreetingsServlet() {
try {
String servletAlias = "/greetings";
/// Since the HTTP Service is available from "http://localhost:8080"
/// the Greetings servlet will be available from
/// "http://localhost:8080/greetings"
getHttpService().registerServlet(servletAlias,
new GreetingsServlet(),
null /* No Init Params we will pass to the servlet */,
null /* By passing null we tell the http service to use
the default http context, we can use the value
getHttpService().createDefaultHttpContext()
to achieve the same result */);
System.out.println("Registered Greetings Servlet with alias: "
+ servletAlias);
} catch (Throwable ex) {
ex.printStackTrace();
}
}
private HttpService httpService;
public HttpService getHttpService() {
return httpService;
}
public void setHttpService(HttpService httpService) {
this.httpService = httpService;
httpServiceUpdated();
}
}
Providing ResourcesTo register resources (e.g., images, css, html, zip files) you need to:
The default HttpContext maps the resource request to the bundle resource using Bundle.getResource(String). The example below registers a resource that provides images contained in the "META-INF/images" directory of the example bundle. Please note that unlike with the example above, the activator is not shown in this one since it performs the same task.
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import ...;
public class ImagesResourceRegistrar {
protected void registerGreetingsServlet() {
try {
String imagesResourceAlias = "/images";
/// Since the HTTP Service is available from
/// "http://localhost:8080", the Images resource will be
/// available on "http://localhost:8080/images" and since
/// we have the files "haruhi.jpg" and "nagato.gif" in the
/// "META-INF/images" directory of the bundle the images
/// will be available on these URLs:
/// http://localhost:8080/images/nagato.gif
/// http://localhost:8080/images/haruhi.jpg
getHttpService().registerResources(imagesResourceAlias,
"Images",
new ImagesResourceHttpContext());
System.out.println("Registered Images Resouce with alias: "
+ imagesResourceAlias);
} catch (Throwable ex) {
ex.printStackTrace();
}
}
protected class ImagesResourceHttpContext implements HttpContext {
public String getMimeType(String name) {
if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
return "image/jpeg";
} else if (name.endsWith(".gif")) {
return "image/gif";
} else {
return null;
}
}
public URL getResource(String name) {
int lastSlashIndex = name.lastIndexOf('/');
/// We need to extract the file name from the resource name.
String fileName = lastSlashIndex == -1 ? name : name.substring(
lastSlashIndex + 1);
System.out.println(String.format(
"Requested Resource: %s, Resource File Name: %s",
name, fileName));
return getBundleContext().getBundle().getResource(
"META-INF/images/" + fileName);
}
public boolean handleSecurity(HttpServletRequest request,
HttpServletResponse response) throws IOException {
/// return true otherwise the HTTP Service will think that
/// security checks has failed.
return true;
}
}
/// We require the bundle context since we need to retrieve
/// images from the bundle.
public ImagesResourceRegistrar(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
private HttpService httpService;
public HttpService getHttpService() {
return httpService;
}
public void setHttpService(HttpService httpService) {
this.httpService = httpService;
httpServiceUpdated();
}
private final BundleContext bundleContext;
protected BundleContext getBundleContext() {
return bundleContext;
}
/// This method is invoked when the HTTP Service is updated.
protected void httpServiceUpdated() {
if (getHttpService() != null) {
registerGreetingsServlet();
}
}
}
HttpContext UsageBelow are the most important usages of HttpContext.
HTTP Service specification ImplementationsThe two most notable implementations of the HTT Service specification are: Equinox HTTP Service and Pax Web project. Pax Web is compatible with all OSGi Frameworks, unlike Equinox HTTP Service. You will find that some of the implementations provide different bundle versions: bundles that provide the HTTP Service implementations, bundles that provide implementations and the API and bundles that provide all mentioned above plus an embedded HTTP Engine. All HTTP Service implementations use Jetty as the HTTP Engine. |