You can now use QBit inside of any servlet engine. QBit supports Servlet Async API (early) as well as Vertx.
You can run QBit standalone with Vertx (and soon Jetty) or you can embed QBit inside of any Servlet container. (Next up: Servlet WebSocket support for QBit).
Qbit servlet support also a preview of using jetty and the QBit microservice lib together
I have a prototype that uses Jetty to prove out that qbit and Servlets is working for REST support.
Simple Service
Simple Service and Service Server construction
package io.advantageous.qbit.servlet.integrationproto;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.http.HttpServer;
import io.advantageous.qbit.server.ServiceServer;
import static io.advantageous.qbit.server.ServiceServerBuilder.serviceServerBuilder;
/**
* Created by rhightower on 2/12/15.
*/
public class MyServiceModule {
@RequestMapping("/ping")
public static class PingService {
@RequestMapping("/ping")
public String ping() {
return "ok";
}
}
public static ServiceServer configureApp(final HttpServer server) {
return serviceServerBuilder().setHttpServer(server)
.build().initServices(new PingService()).startServer();
}
}
The above by design has nothing to do with Jetty or Servlets.
Then to bind it into a Servlet engine for testing, I use Jetty because Jetty is easy to use and embed.
Using Jetty to test QBit servlet support
package io.advantageous.qbit.servlet.integrationproto;
import io.advantageous.qbit.http.HttpServer;
import io.advantageous.qbit.server.ServiceServer;
import io.advantageous.qbit.servlet.QBitHttpServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import javax.servlet.ServletConfig;
/**
* Created by rhightower on 2/12/15.
*/
public class QBitPlusJettyPrototype {
public static class MyQBitServlet extends QBitHttpServlet {
public static final String QBIT_SERVICE_SERVER_SERVER = "QBit.ServiceServer.server";
private ServletConfig config;
@Override
protected void wireHttpServer(final HttpServer httpServer, final ServletConfig config) {
final ServiceServer server = MyServiceModule.configureApp(httpServer);
config.getServletContext().setAttribute(QBIT_SERVICE_SERVER_SERVER, server);
this.config = config;
}
@Override
protected void stop() {
final ServiceServer server =
(ServiceServer)
config.getServletContext().getAttribute(QBIT_SERVICE_SERVER_SERVER);
server.stop();
}
}
public static void main(String... args) throws Exception {
Server server = new Server(8080);
ServletContextHandler servletContextHandler = new ServletContextHandler(server,
"*", true, false);
servletContextHandler.addServlet(MyQBitServlet.class, "/services/*");
server.start();
server.join();
}
}
We just start up Jetty. Tell Jetty that it is a Servlet container. We create a servlet that extends QBitHttpServlet (that is the cross platform way to use QBit in any container, yes.. you could use QBit in Tomcat or Undertow or... God Forbid Websphere or WebLogic).
The support classes for QBit / Servlet support are:
- QBitHttpServlet template design pattern to hook into Servlet lifecycle
- QBitServletUtil utility to convert from HttpServletRequest to QBit HttpRequest
- HttpServletParamMultiMap adapter to a multi-map from a servlet request
- HttpServletHeaderMultiMap ditto but for headers (painful but lightweight)
- ServletHttpServer lightweight HttpServer (this could be in core, it is wafer thin)
Implementation (Early)
I want to put this in qbit-core. It has no ties to Servlets at all.
ServletHttpServer
package io.advantageous.qbit.servlet;
import io.advantageous.qbit.http.HttpRequest;
import io.advantageous.qbit.http.HttpServer;
import io.advantageous.qbit.http.WebSocketMessage;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Created by rhightower on 2/12/15.
*/
public class ServletHttpServer implements HttpServer {
private Consumer<WebSocketMessage> webSocketMessageConsumer = webSocketMessage -> {
};
private Consumer<WebSocketMessage> webSocketCloseMessageConsumer = webSocketMessage -> {
};
private Consumer<HttpRequest> httpRequestConsumer = request -> {
};
private Consumer<Void> requestIdleConsumer = aVoid -> {};
private Consumer<Void> webSocketIdleConsumer = aVoid -> {};
private Predicate<HttpRequest> shouldContinueHttpRequest = request -> true;
private ScheduledFuture<?> future;
@Override
public void setShouldContinueHttpRequest(Predicate<HttpRequest> predicate) {
this.shouldContinueHttpRequest = predicate;
}
public void handleRequest(final HttpRequest request) {
if (shouldContinueHttpRequest.test(request)) {
httpRequestConsumer.accept(request);
}
}
@Override
public void setWebSocketMessageConsumer(final Consumer<WebSocketMessage> webSocketMessageConsumer) {
this.webSocketMessageConsumer = webSocketMessageConsumer;
}
@Override
public void setWebSocketCloseConsumer(Consumer<WebSocketMessage> webSocketCloseMessageConsumer) {
this.webSocketCloseMessageConsumer = webSocketCloseMessageConsumer;
}
@Override
public void setHttpRequestConsumer(Consumer<HttpRequest> httpRequestConsumer) {
this.httpRequestConsumer = httpRequestConsumer;
}
@Override
public void setHttpRequestsIdleConsumer(Consumer<Void> idleConsumer) {
this.requestIdleConsumer = idleConsumer;
}
@Override
public void setWebSocketIdleConsume(Consumer<Void> idleConsumer) {
this.webSocketIdleConsumer = idleConsumer;
}
private ScheduledExecutorService monitor;
@Override
public void start() {
monitor = Executors.newScheduledThreadPool(1,
runnable -> {
Thread thread = new Thread(runnable);
thread.setName("ServletHttpServer Flush Thread" );
return thread;
}
);
/** This wants to be configurable. */
future = monitor.scheduleAtFixedRate(() -> {
try {
requestIdleConsumer.accept(null);
webSocketIdleConsumer.accept(null);
} catch (Exception ex) {
ex.printStackTrace();
//logger.error("blah blah Manager::Problem running queue manager", ex); //TODO log this
}
}, 50, 50, TimeUnit.MILLISECONDS);
}
@Override
public void stop() {
if (future!=null) {
future.cancel(true);
}
if (monitor!=null) {
monitor.shutdown();
}
}
}
A Servlet so someone using Servlets can easily use QBit.
QBitHttpServlet - allows QBit REST to be easily used from Servlets
package io.advantageous.qbit.servlet;
import io.advantageous.qbit.http.HttpRequest;
import io.advantageous.qbit.http.HttpServer;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static io.advantageous.qbit.servlet.QBitServletUtil.convertRequest;
/**
* Created by rhightower on 2/12/15.
*/
@WebServlet(asyncSupported = true)
public abstract class QBitHttpServlet extends HttpServlet {
private final ServletHttpServer httpServer = new ServletHttpServer();
@Override
public void destroy() {
httpServer.stop();
stop();
}
protected abstract void stop();
@Override
public void init(ServletConfig config) throws ServletException {
httpServer.start();
wireHttpServer(httpServer, config);
}
protected abstract void wireHttpServer(final HttpServer httpServer, ServletConfig config);
@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
final HttpRequest httpRequest = convertRequest(request.startAsync());
httpServer.handleRequest(httpRequest);
}
}
Now the meat. This does all the work and glue. This glues the Servlet world to the QBit world.
QBitServletUtil GLUE
package io.advantageous.qbit.servlet;
import io.advantageous.qbit.http.HttpRequest;
import io.advantageous.qbit.http.HttpRequestBuilder;
import io.advantageous.qbit.util.MultiMap;
import org.boon.IO;
import javax.servlet.AsyncContext;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static io.advantageous.qbit.http.HttpRequestBuilder.httpRequestBuilder;
/**
* Created by rhightower on 2/12/15.
*/
public class QBitServletUtil {
public static HttpRequest convertRequest(final AsyncContext asyncContext) {
final HttpServletRequest request = (HttpServletRequest) asyncContext.getRequest();
final HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
final MultiMap<String, String> headers = new HttpServletHeaderMultiMap(request);
final MultiMap<String, String> params = new HttpServletParamMultiMap(request);
final HttpRequestBuilder httpRequestBuilder =
httpRequestBuilder().setParams(params)
.setHeaders(headers).setUri(request.getRequestURI())
.setMethod(request.getMethod());
setRequestBodyIfNeeded(request, httpRequestBuilder);
setupRequestHandler(asyncContext, response, httpRequestBuilder);
return httpRequestBuilder.build();
}
private static void setupRequestHandler(AsyncContext asyncContext, HttpServletResponse response, HttpRequestBuilder httpRequestBuilder) {
httpRequestBuilder.setTextResponse((code, contentType, body) -> {
response.setHeader("Content-Type", contentType);
try {
final ServletOutputStream outputStream = response.getOutputStream();
IO.write(outputStream, body);
outputStream.close();
asyncContext.complete();
} catch (IOException e) {
throw new IllegalStateException(e);
}
});
}
private static void setRequestBodyIfNeeded(HttpServletRequest request, HttpRequestBuilder httpRequestBuilder) {
if (request.getMethod().equals("POST") || request.getMethod().equals("PUT")) {
final String body = readBody(request);
if (body!=null) {
httpRequestBuilder.setBody(body);
}
}
}
private static String readBody(HttpServletRequest request) {
try {
final ServletInputStream inputStream = request.getInputStream();
final String body = IO.read(inputStream, StandardCharsets.UTF_8);
return body;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
What is QBit again?
QBit is a queuing library for microservices. It is similar to many other projects like Akka, Spring Reactor, etc. QBit is just a library not a platform. QBit has libraries to put a service behind a queue. You can use QBit queues directly or you can create a service. QBit services can be exposed by WebSocket, HTTP, HTTP pipeline, and other types of remoting. A service in QBit is a Java class whose methods are executed behind service queues. QBit implements apartment model threading and is similar to the Actor model or a better description would be Active Objects. QBit does not use a disruptor. It uses regular Java Queues. QBit can do north of 100 million ping pong calls per second which is an amazing speed (seen as high as 200M). QBit also supports calling services via REST, and WebSocket. QBit is microservices in the pure Web sense: JSON, HTTP, WebSocket, etc.
QBit lingo
QBit is a Java microservice lib supporting REST, JSON and WebSocket. It is written in Java but I might one day write a version in Rust or Go or C# (but that would require a large payday).
Service POJO (plain old Java object) behind a queue that can receive method calls via proxy calls or events (May have one thread managing events, method calls, and responses or two one for method calls and events and the other for responses so response handlers do not block service. One is faster unless responses block). Services can use Spring MVC style REST annotations to expose themselves to the outside world via REST and WebSocket.
ServiceBundle Many POJOs behind one response queue and many receive queues. There may be one thread for all responses or not. They also can be one receive queue.
Queue A thread managing a queue. It supports batching. It has events for empty, reachedLimit, startedBatch, idle. You can listen to these events from services that sit behind a queue. You don't have to use Services. You can use Queue's direct.
ServiceServer ServiceBundle that is exposed to REST and WebSocket communication
EventBus EventBus is a way to send a lot of messages to services that may be loosely coupled
ClientProxy Way to invoke service through async interface, service can be inproc (same process) or remoted over WebSocket.
Non-blocking QBit is a non-blocking lib. You use CallBacks via Java 8 Lambdas. You can also send event messages and get replies. Messaging is built into the system so you can easily coordinate complex tasks.
Speed There is a lot of room for improvement with Speed. But already QBit is VERY fast. 200M+ TPS inproc ping pong, 10M-20M+ TPS event bus, 500K TPS RPC calls over WebSocket/JSON, etc. More work needs to be done to improve speed, but now it is fast enough where I am working more with usability.
Besides if I make it too fast, then no one will publish benchmarks, and then I can't publish follow up benchmarks that kick their ass. Where would be the fun? There is no story if their is no conflict.
Learn more about QBit:
[Detailed Tutorial] QBit microservice example
No comments:
Post a Comment