Core Concepts
Servlet Integration
Soklet is not a Servlet Container - it has its own in-process HTTP server, its own approach to request and response constructs, and so forth. Soklet applications are intended to be "vanilla" Java applications, as opposed to a WAR file deployed onto a Java EE App Server.
However, there is a large body of existing code that relies on the Servlet API. To support it, Soklet provides its own implementations of the following Servlet interfaces, which enable interoperability for many common use cases:
HttpServletRequestHttpServletResponseHttpSessionHttpSessionContext(only available forjavax.servlet)ServletContextServletInputStreamServletOutputStreamServletPrintWriter
Support is available for both legacy javax.servlet and current jakarta.servlet specifications. Just like Soklet, these integrations have zero dependencies (not counting Soklet itself and the javax.servlet-api/jakarta.servlet-api spec JARs) - drop the appropriate JAR into your project and you're good to go.
Installation
Maven
If you use javax.servlet:
<dependency>
<groupId>com.soklet</groupId>
<artifactId>soklet-servlet-javax</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.soklet</groupId>
<artifactId>soklet-servlet-javax</artifactId>
<version>1.0.0</version>
</dependency>
If you use jakarta.servlet:
<dependency>
<groupId>com.soklet</groupId>
<artifactId>soklet-servlet-jakarta</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.soklet</groupId>
<artifactId>soklet-servlet-jakarta</artifactId>
<version>1.0.0</version>
</dependency>
Gradle
If you use javax.servlet:
repositories {
mavenCentral()
}
dependencies {
implementation 'com.soklet:soklet-servlet-javax:1.0.0'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.soklet:soklet-servlet-javax:1.0.0'
}
If you use jakarta.servlet:
repositories {
mavenCentral()
}
dependencies {
implementation 'com.soklet:soklet-servlet-jakarta:1.0.0'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.soklet:soklet-servlet-jakarta:1.0.0'
}
Usage
A normal Servlet API integration looks like the following:
- Given a Soklet
Request, create both anHttpServletRequestand anHttpServletResponse. - Write whatever is needed to
HttpServletResponse - Convert the
HttpServletResponseto a SokletMarshaledResponse
@GET("/servlet-example")
public MarshaledResponse servletExample(Request request) {
// Create an HttpServletRequest from the Soklet Request
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request).build();
// Create an HttpServletResponse from the Soklet Request
SokletHttpServletResponse httpServletResponse =
SokletHttpServletResponse.withRequest(request);
// Write some data to the response using Servlet APIs
Cookie cookie = new Cookie("name", "value");
cookie.setDomain("soklet.com");
cookie.setMaxAge(60);
cookie.setPath("/");
httpServletResponse.setStatus(200);
httpServletResponse.addHeader("test", "one");
httpServletResponse.addHeader("test", "two");
httpServletResponse.addCookie(cookie);
httpServletResponse.setCharacterEncoding("ISO-8859-1");
httpServletResponse.getWriter().print("test");
// Convert HttpServletResponse into a Soklet MarshaledResponse and return it
return httpServletResponse.toMarshaledResponse();
}
@GET("/servlet-example")
public MarshaledResponse servletExample(Request request) {
// Create an HttpServletRequest from the Soklet Request
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request).build();
// Create an HttpServletResponse from the Soklet Request
SokletHttpServletResponse httpServletResponse =
SokletHttpServletResponse.withRequest(request);
// Write some data to the response using Servlet APIs
Cookie cookie = new Cookie("name", "value");
cookie.setDomain("soklet.com");
cookie.setMaxAge(60);
cookie.setPath("/");
httpServletResponse.setStatus(200);
httpServletResponse.addHeader("test", "one");
httpServletResponse.addHeader("test", "two");
httpServletResponse.addCookie(cookie);
httpServletResponse.setCharacterEncoding("ISO-8859-1");
httpServletResponse.getWriter().print("test");
// Convert HttpServletResponse into a Soklet MarshaledResponse and return it
return httpServletResponse.toMarshaledResponse();
}
Instantiation
Soklet's Servlet API types are final and prefer static factory methods to constructors.
Here's how to acquire an instance of each.
HttpServletRequest
Standard approach, uses defaults:
@GET("/servlet-example")
public MarshaledResponse servletExample(Request request) {
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request).build();
// ... rest of method elided ...
}
@GET("/servlet-example")
public MarshaledResponse servletExample(Request request) {
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request).build();
// ... rest of method elided ...
}
Custom approach:
@GET("/servlet-example")
public MarshaledResponse servletExample(Request request) {
// Get references to some dependencies...
ServletContext servletContext = SokletServletContext.withDefaults();
HttpSession httpSession = SokletHttpSession.withServletContext(servletContext);
// ...and supply to the builder:
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request)
.port(1234)
.host("0.0.0.0")
.servletContext(servletContext)
.httpSession(httpSession)
.build();
// ... rest of method elided ...
}
@GET("/servlet-example")
public MarshaledResponse servletExample(Request request) {
// Get references to some dependencies...
ServletContext servletContext = SokletServletContext.withDefaults();
HttpSession httpSession = SokletHttpSession.withServletContext(servletContext);
// ...and supply to the builder:
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request)
.port(1234)
.host("0.0.0.0")
.servletContext(servletContext)
.httpSession(httpSession)
.build();
// ... rest of method elided ...
}
HttpServletResponse
Standard approach:
@GET("/servlet-example")
public MarshaledResponse servletExample(Request request) {
HttpServletResponse response =
SokletHttpServletResponse.withRequest(request);
// ... rest of method elided ...
}
@GET("/servlet-example")
public MarshaledResponse servletExample(Request request) {
HttpServletResponse response =
SokletHttpServletResponse.withRequest(request);
// ... rest of method elided ...
}
Explicit request path:
// Request path must always starts with "/"
HttpServletResponse response =
SokletHttpServletResponse.withRequestPath("/test/abc");
// Request path must always starts with "/"
HttpServletResponse response =
SokletHttpServletResponse.withRequestPath("/test/abc");
HttpSession
// Sessions need a context...
ServletContext servletContext = SokletServletContext.withDefaults();
// ...so we get one and wire it in:
HttpSession httpSession =
SokletHttpSession.withServletContext(servletContext);
// Sessions need a context...
ServletContext servletContext = SokletServletContext.withDefaults();
// ...so we get one and wire it in:
HttpSession httpSession =
SokletHttpSession.withServletContext(servletContext);
HttpSessionContext
Deprecation Warning
The Servlet spec has marked javax.servlet.http.HttpSessionContext as deprecated since Servlet API 2.1 with no replacement, so it's unlikely you will need to use this type - but it's available regardless.
This type is not available for jakarta.servlet integration.
HttpSessionContext httpSessionContext = SokletHttpSessionContext.withDefaults();
HttpSessionContext httpSessionContext = SokletHttpSessionContext.withDefaults();
ServletContext
Standard approach:
ServletContext servletContext = SokletServletContext.withDefaults();
ServletContext servletContext = SokletServletContext.withDefaults();
Explicit java.io.Writer which acts as a sink for ServletContext::log(String) and ServletContext::log(String, Throwable) invocations:
try(FileWriter logWriter = new FileWriter("example.txt")) {
ServletContext servletContext =
SokletServletContext.withLogWriter(logWriter);
// Fire off a log event to be consumed by the writer
servletContext.log("Just a test");
}
try(FileWriter logWriter = new FileWriter("example.txt")) {
ServletContext servletContext =
SokletServletContext.withLogWriter(logWriter);
// Fire off a log event to be consumed by the writer
servletContext.log("Just a test");
}
ServletInputStream
try(InputStream inputStream = ...) {
ServletInputStream servletInputStream =
SokletServletInputStream.withInputStream(inputStream);
// TODO: do some work with servletInputStream
}
try(InputStream inputStream = ...) {
ServletInputStream servletInputStream =
SokletServletInputStream.withInputStream(inputStream);
// TODO: do some work with servletInputStream
}
ServletOutputStream
Standard approach:
try(OutputStream outputStream = ...) {
ServletOutputStream servletOutputStream =
SokletServletOutputStream.withOutputStream(outputStream);
// TODO: do some work with servletOutputStream
}
try(OutputStream outputStream = ...) {
ServletOutputStream servletOutputStream =
SokletServletOutputStream.withOutputStream(outputStream);
// TODO: do some work with servletOutputStream
}
With listeners:
try(OutputStream outputStream = ...) {
ServletOutputStream servletOutputStream =
SokletServletOutputStream.withOutputStream(outputStream)
.onWriteOccurred((servletOutputStream, writtenByte) -> {
// Take action when a write occurred
})
.onWriteFinalized((servletOutputStream) -> {
// Take action when the writes have been finalized (totally done)
})
.build();
// TODO: do some work with servletOutputStream
}
try(OutputStream outputStream = ...) {
ServletOutputStream servletOutputStream =
SokletServletOutputStream.withOutputStream(outputStream)
.onWriteOccurred((servletOutputStream, writtenByte) -> {
// Take action when a write occurred
})
.onWriteFinalized((servletOutputStream) -> {
// Take action when the writes have been finalized (totally done)
})
.build();
// TODO: do some work with servletOutputStream
}
ServletPrintWriter
Standard approach:
try(Writer writer = ...) {
ServletPrintWriter servletPrintWriter =
SokletServletPrintWriter.withWriter(writer).build();
// TODO: do some work with writer
}
try(Writer writer = ...) {
ServletPrintWriter servletPrintWriter =
SokletServletPrintWriter.withWriter(writer).build();
// TODO: do some work with writer
}
With listeners:
try(Writer writer = ...) {
ServletPrintWriter servletPrintWriter =
SokletServletPrintWriter.withWriter(writer)
.onWriteOccurred((servletPrintWriter, writerEvent) -> {
// Take action when a write occurred
})
.onWriteFinalized((servletPrintWriter) -> {
// Take action when the writes have been finalized (totally done)
})
.build();
// TODO: do some work with servletPrintWriter
}
try(Writer writer = ...) {
ServletPrintWriter servletPrintWriter =
SokletServletPrintWriter.withWriter(writer)
.onWriteOccurred((servletPrintWriter, writerEvent) -> {
// Take action when a write occurred
})
.onWriteFinalized((servletPrintWriter) -> {
// Take action when the writes have been finalized (totally done)
})
.build();
// TODO: do some work with servletPrintWriter
}
Real-World Example
Suppose you'd like to have your application function as a SAML Service Provider (SP), where you redirect to a SAML Identity Provider (IdP) to perform authentication and rely on it to POST back a cryptographically-signed assertion confirming the user is who she says she is.
You might prefer to use a library like OneLogin's SAML Java Toolkit instead of writing your own SP implementation. Because this library is designed specifically for the Servlet API, Soklet's Servlet integration is necessary to use it.
Here's how an integration might look.
First, we need to be able to redirect to the IdP for login:
@GET("/saml/redirect-to-login")
public MarshaledResponse redirectToSamlLogin(Request request) {
// Create an HttpServletRequest from the Soklet Request
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request).build();
// Create an HttpServletResponse from the Soklet Request
SokletHttpServletResponse httpServletResponse =
SokletHttpServletResponse.withRequest(request);
// Configure SAML Java Toolkit (details not shown)...
Saml2Settings settings = acquireSaml2Settings();
// ...and let it do its work on the HttpServletRequest/HttpServletResponse.
// Behind the scenes, SAML Java Toolkit generates a cryptographically-
// signed URL and writes headers to perform an HTTP 302 redirect to it
Auth auth = new Auth(settings, httpServletRequest, httpServletResponse);
auth.login("https://my.example.api/saml/process-assertion");
// Convert HttpServletResponse into a MarshaledResponse and return it.
// Any headers/cookies/body/etc. written to the HttpServletResponse
// are carried over into the Soklet marshaled-response representation
return httpServletResponse.toMarshaledResponse();
}
@GET("/saml/redirect-to-login")
public MarshaledResponse redirectToSamlLogin(Request request) {
// Create an HttpServletRequest from the Soklet Request
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request).build();
// Create an HttpServletResponse from the Soklet Request
SokletHttpServletResponse httpServletResponse =
SokletHttpServletResponse.withRequest(request);
// Configure SAML Java Toolkit (details not shown)...
Saml2Settings settings = acquireSaml2Settings();
// ...and let it do its work on the HttpServletRequest/HttpServletResponse.
// Behind the scenes, SAML Java Toolkit generates a cryptographically-
// signed URL and writes headers to perform an HTTP 302 redirect to it
Auth auth = new Auth(settings, httpServletRequest, httpServletResponse);
auth.login("https://my.example.api/saml/process-assertion");
// Convert HttpServletResponse into a MarshaledResponse and return it.
// Any headers/cookies/body/etc. written to the HttpServletResponse
// are carried over into the Soklet marshaled-response representation
return httpServletResponse.toMarshaledResponse();
}
Next, we need a way to process the XML assertion sent back to us by the IdP after a successful login:
@POST("/saml/process-assertion")
public Response processSamlAssertion(
Request request,
MyExampleBackend myExampleBackend
) {
// Create an HttpServletRequest from the Soklet Request
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request).build();
// Create an HttpServletResponse from the Soklet Request
SokletHttpServletResponse httpServletResponse =
SokletHttpServletResponse.withRequest(request);
// Configure SAML Java Toolkit (details not shown)...
Saml2Settings settings = acquireSaml2Settings();
// ...and let it do its work.
// Here, SAML Java Toolkit parses the assertion and ensures that
// the IdP's private key was used to sign it.
Auth auth = new Auth(settings, httpServletRequest, httpServletResponse);
auth.processResponse();
if (auth.isAuthenticated()) {
// Everything looks good...
// 1. Pull the user identifier out of the assertion
// 2. Use it to generate a JWT
// 3. Write the JWT to a cookie
// 4. Redirect the user somewhere meaningful
String jwt = myExampleBackend.jwtForNameId(auth.getNameId());
return Response.withRedirect(
RedirectType.HTTP_307_TEMPORARY_REDIRECT,
"https://my.example.website/home"
).cookies(Set.of(
ResponseCookie.with("Access-Token", jwt).build()
))
.build();
} else {
// Real systems should handle this more gracefully
throw new RuntimeException("The SAML assertion is invalid");
}
}
@POST("/saml/process-assertion")
public Response processSamlAssertion(
Request request,
MyExampleBackend myExampleBackend
) {
// Create an HttpServletRequest from the Soklet Request
HttpServletRequest httpServletRequest =
SokletHttpServletRequest.withRequest(request).build();
// Create an HttpServletResponse from the Soklet Request
SokletHttpServletResponse httpServletResponse =
SokletHttpServletResponse.withRequest(request);
// Configure SAML Java Toolkit (details not shown)...
Saml2Settings settings = acquireSaml2Settings();
// ...and let it do its work.
// Here, SAML Java Toolkit parses the assertion and ensures that
// the IdP's private key was used to sign it.
Auth auth = new Auth(settings, httpServletRequest, httpServletResponse);
auth.processResponse();
if (auth.isAuthenticated()) {
// Everything looks good...
// 1. Pull the user identifier out of the assertion
// 2. Use it to generate a JWT
// 3. Write the JWT to a cookie
// 4. Redirect the user somewhere meaningful
String jwt = myExampleBackend.jwtForNameId(auth.getNameId());
return Response.withRedirect(
RedirectType.HTTP_307_TEMPORARY_REDIRECT,
"https://my.example.website/home"
).cookies(Set.of(
ResponseCookie.with("Access-Token", jwt).build()
))
.build();
} else {
// Real systems should handle this more gracefully
throw new RuntimeException("The SAML assertion is invalid");
}
}

