Javalin
Javalin是一个轻量级http框架,我们可以很容易的了解请求的处理过程及其设计,具有较高的学习意义。
从demo说起
public static void main(String[] args) {
Javalin app = Javalin.create(config -> {
System.out.println("用户配置");
});
app.get("/", ctx -> {
System.out.println("do" + ctx.path());
ctx.result("Hello World");
});
// 处理异常
app.exception(Exception.class, (e, ctx) -> {
ctx.result("发生异常了");
});
// 处理异常状态
app.error(404, ctx -> {
ctx.html("Generic 404 message");
});
// 测试异常
app.get("/exception/{e}", ctx -> {
String msg = ctx.pathParam("e");
if ("1".equals(msg)) {
throw new InvalidAlgorithmParameterException("测试异常");
} else if ("2".equals(msg)) {
throw new ClassNotFoundException("Invalid");
} else if ("3".equals(msg)) {
ctx.status(404);
} else {
throw new Exception("Invalid algorithm parameter");
}
});
//get请求{}参数解析
app.get("/path/<path>", ctx -> ctx.result("Hello Path2 " + ctx.pathParam("path")));
//post请求
app.post("/json", ctx -> {
Map<String, String> map = new HashMap<>();
map.put("name", "张三");
ctx.json(map);
});
//get请求<>参数解析
app.get("/name/{name}", ctx -> {
NaiveRateLimit.requestPerTimeUnit(ctx, 5, TimeUnit.MINUTES);
ctx.result("Hello Path " + ctx.pathParam("name"));
});
// webscoket
app.ws("/websocket/{path}", ws -> {
ws.onConnect(ctx -> System.out.println("Connected"));
ws.onMessage(ctx -> {
System.out.println("收到消息" + ctx.message());
ctx.send("我收到了" + ctx.message());
});
});
// 前置处理器
app.before(ctx -> System.out.println("before" + ctx.path()));
// 后置处理器
app.after(ctx -> System.out.println("after" + ctx.path()));
// 启动
app.start(7070);
}
创建Javalin实例
使用代码
Javalin app = Javalin.create(config -> {
System.out.println("用户配置");
});
实现原理
实现源码
public static Javalin create(Consumer<JavalinConfig> config) {
// 初始化示例对象
Javalin app = new Javalin();
// 加载用户配置
JavalinConfig.applyUserConfig(app, app.cfg, config); // mutates app.config and app (adds http-handlers)
// 延迟检测服务是否正常启动
JettyUtil.maybeLogIfServerNotStarted(app.jettyServer);
return app;
}
流程
get/post/websocket/before/after配置
使用方式
app.get("/", ctx -> {
System.out.println("do" + ctx.path());
ctx.result("Hello World");
});
实现源码
public Javalin get(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.GET, path, handler);
}
public Javalin addHandler(@NotNull HandlerType httpMethod, @NotNull String path, @NotNull Handler handler) {
return addHandler(httpMethod, path, handler, new RouteRole[0]); // no roles set for this route (open to everyone with default access manager)
}
public Javalin addHandler(@NotNull HandlerType handlerType, @NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
Set<RouteRole> roleSet = new HashSet<>(Arrays.asList(roles));
javalinServlet.getMatcher().add(new HandlerEntry(handlerType, path, cfg.routing, roleSet, handler));
eventManager.fireHandlerAddedEvent(new HandlerMetaInfo(handlerType, Util.prefixContextPath(cfg.routing.contextPath, path), handler, roleSet));
return this;
}
添加流程
get/post/websocket/before/after处理器都是这个类型,只是请求类型不一致,
http支持的请求类型有
GET, POST, PUT, PATCH, DELETE, HEAD, TRACE, CONNECT, OPTIONS, BEFORE, AFTER, INVALID;
websocket支持的请求类型有
WS_BEFORE, WEBSOCKET, WS_AFTER
exceptionHandler配置
使用方式
// 处理异常
app.exception(Exception.class, (e, ctx) -> {
ctx.result("发生异常了");
});
实现源码
public <T extends Exception> Javalin exception(@NotNull Class<T> exceptionClass, @NotNull ExceptionHandler<? super T> exceptionHandler) {
javalinServlet.getExceptionMapper().getHandlers().put(exceptionClass, (ExceptionHandler<Exception>) exceptionHandler);
return this;
}
添加流程
errorHandler配置
使用方式
// 处理异常状态
app.error(404, ctx -> {
ctx.html("Generic 404 message");
});
实现源码
public Javalin error(int status, @NotNull String contentType, @NotNull Handler handler) {
javalinServlet.getErrorMapper().addHandler(status, contentType, handler);
return this;
}
添加流程
权限管理配置
使用方式
// Set the access-manager that Javalin should use
config.accessManager((handler, ctx, routeRoles) -> {
MyRole userRole = getUserRole(ctx);
if (routeRoles.contains(userRole)) {
handler.handle(ctx);
} else {
ctx.status(401).result("Unauthorized");
}
});
Role getUserRole(Context ctx) {
// determine user role based on request.
// typically done by inspecting headers, cookies, or user session
}
enum Role implements RouteRole {
ANYONE, ROLE_ONE, ROLE_TWO, ROLE_THREE;
}
app.get("/un-secured", ctx -> ctx.result("Hello"), Role.ANYONE);
app.get("/secured", ctx -> ctx.result("Hello"), Role.ROLE_ONE);
实现源码
fun accessManager(accessManager: AccessManager) { pvt.accessManager = accessManager }
设置流程
启动流程
使用方式
// 启动
app.start(7070);
源码
public Javalin start(int port) {
// 设置端口号
jettyServer.setServerPort(port);
// 启动服务
return start();
}
start()
public Javalin start() {
// 记录启动开始时间
long startupTimer = System.currentTimeMillis();
// 如果已经启动了就报错
if (jettyServer.started) {
String message = "Server already started. If you are trying to call start() on an instance " +
"of Javalin that was stopped using stop(), please create a new instance instead.";
throw new IllegalStateException(message);
}
// 标识服务已经启动
jettyServer.started = true;
// 检测日志组件是否引入
Util.printHelpfulMessageIfLoggerIsMissing();
// 触发服务启动中事件
eventManager.fireEvent(JavalinEvent.SERVER_STARTING);
try {
// 打印服务启动中
JavalinLogger.startup("Starting Javalin ...");
// 启动jetty服务
jettyServer.start(javalinJettyServlet);
// 打印javlin和java版本
Util.logJavalinVersion();
// 打印服务已经启动和耗时
JavalinLogger.startup("Javalin started in " + (System.currentTimeMillis() - startupTimer) + "ms \\o/");
// 发布服务已启动事件
eventManager.fireEvent(JavalinEvent.SERVER_STARTED);
} catch (Exception e) {
// 打印服务启动异常
JavalinLogger.error("Failed to start Javalin");
// 发布服务启动失败事件
eventManager.fireEvent(JavalinEvent.SERVER_START_FAILED);
// 如果jetty已启动成功了,则停止服务
if (Boolean.TRUE.equals(jettyServer.server().getAttribute("is-default-server"))) {
stop();// stop if server is default server; otherwise, the caller is responsible to stop
}
if (e.getMessage() != null && e.getMessage().contains("Failed to bind to")) {
// 端口冲突
throw new JavalinBindException("Port already in use. Make sure no other process is using port " + Util.getPort(e) + " and try again.", e);
} else if (e.getMessage() != null && e.getMessage().contains("Permission denied")) {
// 权限不足
throw new JavalinBindException("Port 1-1023 require elevated privileges (process must be started by admin).", e);
}
// 其他异常
throw new JavalinException(e);
}
return this;
}
其中核心逻辑在jettyServer.start(javalinJettyServlet);中
jettyServer.start(javalinJettyServlet);
fun start(wsAndHttpServlet: JavalinJettyServlet) {
// 如果未设置端口则设置为8080端口
if (serverPort == -1 && cfg.pvt.server == null) {
serverPort = 8080
JavalinLogger.startup("No port specified, starting on port $serverPort. Call start(port) to change ports.")
}
// 初始化默认sessionHandler管理session
cfg.pvt.sessionHandler = cfg.pvt.sessionHandler ?: defaultSessionHandler()
val nullParent = null // javalin handlers are orphans
// 定义jetty请求处理器
val wsAndHttpHandler = object : ServletContextHandler(nullParent, Util.normalizeContextPath(cfg.routing.contextPath), SESSIONS) {
override fun doHandle(target: String, jettyRequest: Request, request: HttpServletRequest, response: HttpServletResponse) {
request.setAttribute("jetty-target-and-request", Pair(target, jettyRequest)) // used in JettyResourceHandler
nextHandle(target, jettyRequest, request, response)
}
}.apply {
this.sessionHandler = cfg.pvt.sessionHandler
cfg.pvt.servletContextHandlerConsumer?.accept(this)
// 设置所有路径处理器
addServlet(ServletHolder(wsAndHttpServlet), "/*")
// 初始化websocket处理器
JettyWebSocketServletContainerInitializer.configure(this) { _, _ ->
/* we don't want to configure WebSocketMappings during ServletContext initialization phase */
}
}
// 初始化jetty服务,并设置处理器
server().apply {
handler = if (handler == null) wsAndHttpHandler else handler.attachHandler(wsAndHttpHandler)
if (connectors.isEmpty()) { // user has not added their own connectors, we add a single HTTP connector
connectors = arrayOf(defaultConnector(this))
}
}.start()
// 打印javalin logo
logJavalinBanner(cfg.showJavalinBanner)
// 打印是否使用Loom(Java虚拟线程)
LoomUtil.logIfLoom(server())
// 初始化资源处理器
(cfg.pvt.resourceHandler as? JettyResourceHandler)?.init() // we want to init this here to get logs in order
// 打印服务使用的ip和端口
server().connectors.filterIsInstance<ServerConnector>().forEach {
JavalinLogger.startup("Listening on ${it.protocol}://${it.host ?: "localhost"}:${it.localPort}${cfg.routing.contextPath}")
}
// 非http服务打印绑定的端口
server().connectors.filter { it !is ServerConnector }.forEach {
JavalinLogger.startup("Binding to: $it")
}
// 设置服务启动的端口号
serverPort = (server().connectors[0] as? ServerConnector)?.localPort ?: -1
}
启动流程
请求处理流程
处理请求的入口
JavalinJettyServlet.service
这个类既处理http也处理websocket
override fun service(req: HttpServletRequest, res: HttpServletResponse) { // this handles both http and websocket
// 判断是否有websocket请求标识
if (req.getHeader(Header.SEC_WEBSOCKET_KEY) == null) { // this isn't a websocket request
// http请求处理
return httpServlet.service(req, res) // treat as normal HTTP request
}
// 去除项目路径前缀
val requestUri = req.requestURI.removePrefix(req.contextPath)
// 查找url对应后端处理器
val entry = wsPathMatcher.findEndpointHandlerEntry(requestUri) ?: return res.sendError(404, "WebSocket handler not found")
// 构建请求处理的上下文
val upgradeContext = JavalinServletContext(
req = req,
res = res,
cfg = cfg,
matchedPath = entry.path,
pathParamMap = entry.extractPathParams(requestUri),
)
// 校验权限
if (!allowedByAccessManager(entry, upgradeContext)) return res.sendError(HttpStatus.UNAUTHORIZED.code, HttpStatus.UNAUTHORIZED.message)
// 设置上下文
req.setAttribute(upgradeContextKey, upgradeContext)
// 设置协议头
setWsProtocolHeader(req, res)
// 处理websocket请求
super.service(req, res) // everything is okay, perform websocket upgrade
}
从源码可以知道http请求又调了JavalinServlet.service进行处理,websocket
掉jetty进行处理,会回调这个类型configure方法,因为这类继承了JettyWebSocketServlet类。
接下来先介绍http请求处理部分
JavalinServlet.service
override fun service(request: HttpServletRequest, response: HttpServletResponse) {
try {
// 构建请求处理的上下文
val ctx = JavalinServletContext(req = request, res = response, cfg = cfg)
// 定义请求处理的任务列表添加函数,添加到头部还是尾部
val submitTask: (SubmitOrder, Task) -> Unit = { order, task ->
when (order) {
FIRST -> ctx.tasks.offerFirst(task)
LAST -> ctx.tasks.add(task)
}
}
// 去除项目的上下文路径
val requestUri = ctx.path().removePrefix(ctx.contextPath())
// 生成请求处理的任务列表(生成任务的最终顺序如下表)
cfg.pvt.servletRequestLifecycle.forEach { it.createTasks(submitTask, this, ctx, requestUri) }
// 处理请求
ctx.handleSync()
} catch (throwable: Throwable) {
// 兜底异常处理
exceptionMapper.handleUnexpectedThrowable(response, throwable)
}
}
处理器类型 | 是否跳过异常 |
---|---|
BEFORE | 是 |
HTTP | 是 |
ERROR | 否 |
AFTER | 否 |
再看下处理请求部分
private fun JavalinServletContext.handleSync() {
while (userFutureSupplier == null && tasks.isNotEmpty()) {
// 取出第一个任务
val task = tasks.poll()
// 判断是否发生过异常且是否跳过异常
if (exceptionOccurred && task.skipIfExceptionOccurred) {
continue
}
// 处理任务
handleTask(task.handler)
}
when {
// 异步处理的请求
userFutureSupplier != null -> handleUserFuture()
// 非异步处理的请求,写入结果并打印日志
else -> writeResponseAndLog()
}
}
private fun <R> JavalinServletContext.handleTask(handler: TaskHandler<R>): R? =
try {
handler.handle()
} catch (throwable: Throwable) {
exceptionOccurred = true
userFutureSupplier = null
tasks.offerFirst(Task(skipIfExceptionOccurred = false) { exceptionMapper.handle(this, throwable) })
null
}
然后在看下websocket的处理过程
首先调用configure完成连接初始化
JavalinJettyServlet.configure
// websocket初始化连接
override fun configure(factory: JettyWebSocketServletFactory) { // this is called once, before everything
cfg.pvt.wsFactoryConfig?.accept(factory)
factory.setCreator(JettyWebSocketCreator { req, _ -> // this is called when a websocket is created (after [service])
val preUpgradeContext = req.httpServletRequest.getAttribute(upgradeContextKey) as JavalinServletContext
req.httpServletRequest.setAttribute(upgradeContextKey, preUpgradeContext.changeBaseRequest(req.httpServletRequest))
val session = req.session as? Session?
req.httpServletRequest.setAttribute(upgradeSessionAttrsKey, session?.attributeNames?.asSequence()?.associateWith { session.getAttribute(it) })
// 初始化连接
return@JettyWebSocketCreator WsConnection(wsPathMatcher, wsExceptionMapper, cfg.pvt.wsLogger)
})
}
重点看下WsConnection类
@OnWebSocketConnect
fun onConnect(session: Session) {
// websocket连接初始化
val ctx = WsConnectContext(sessionId, session)
tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsConnectHandler?.handleConnect(ctx) }
tryAfterHandlers(ctx) { it.wsConfig.wsConnectHandler?.handleConnect(ctx) }
wsLogger?.wsConnectHandler?.handleConnect(ctx)
}
@OnWebSocketMessage
fun onMessage(session: Session, message: String) {
// 收到文本消息
val ctx = WsMessageContext(sessionId, session, message)
tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsMessageHandler?.handleMessage(ctx) }
tryAfterHandlers(ctx) { it.wsConfig.wsMessageHandler?.handleMessage(ctx) }
wsLogger?.wsMessageHandler?.handleMessage(ctx)
}
@OnWebSocketMessage
fun onMessage(session: Session, buffer: ByteArray, offset: Int, length: Int) {
// 收到二进制消息
val ctx = WsBinaryMessageContext(sessionId, session, buffer, offset, length)
tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsBinaryMessageHandler?.handleBinaryMessage(ctx) }
tryAfterHandlers(ctx) { it.wsConfig.wsBinaryMessageHandler?.handleBinaryMessage(ctx) }
wsLogger?.wsBinaryMessageHandler?.handleBinaryMessage(ctx)
}
@OnWebSocketClose
fun onClose(session: Session, statusCode: Int, reason: String?) {
// 连接关闭
val ctx = WsCloseContext(sessionId, session, statusCode, reason)
tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsCloseHandler?.handleClose(ctx) }
tryAfterHandlers(ctx) { it.wsConfig.wsCloseHandler?.handleClose(ctx) }
wsLogger?.wsCloseHandler?.handleClose(ctx)
ctx.disableAutomaticPings()
}
@OnWebSocketError
fun onError(session: Session, throwable: Throwable?) {
// 发生异常
val ctx = WsErrorContext(sessionId, session, throwable)
tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsErrorHandler?.handleError(ctx) }
tryAfterHandlers(ctx) { it.wsConfig.wsErrorHandler?.handleError(ctx) }
wsLogger?.wsErrorHandler?.handleError(ctx)
}
采用注解形式定义一系列websocket操作
请求处理流程
总结
至此,主要处理流程都介绍完毕,还有部分异步请求的处理下次再更新。最后附上Jetty核心组件结构图