first version
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
*/
|
||||
|
||||
package simapi.middlewares
|
||||
|
||||
import soulsoft_web_http.*
|
||||
import simapi.communications.*
|
||||
import simapi.helpers.*
|
||||
|
||||
/**
|
||||
* 认证信息获取中间件:从 Header Token 或 Query token 解析登录信息并注入上下文。
|
||||
* 对应 C# 的 SimApi.Middlewares.SimApiAuthMiddleware。
|
||||
*/
|
||||
public class SimApiAuthMiddleware <: IMiddleware {
|
||||
private let _auth: SimApiAuth
|
||||
|
||||
public init(auth: SimApiAuth) {
|
||||
_auth = auth
|
||||
}
|
||||
|
||||
/**
|
||||
* 中间件入口(IMiddleware 风格,对齐 C# UseMiddleware<T> 的 InvokeAsync)。
|
||||
*/
|
||||
public func invoke(context: HttpContext, next: RequestDelegate): Unit {
|
||||
var token = context.request.headers.get("Token")
|
||||
if (token == None || token == Some("")) {
|
||||
token = context.request.query.get("token")
|
||||
}
|
||||
if (let Some(token) <- token) {
|
||||
if (!token.isEmpty()) {
|
||||
if (let Some(login) <- _auth.getLogin(token)) {
|
||||
context.items["LoginToken"] = token
|
||||
context.items["LoginInfo"] = login
|
||||
}
|
||||
}
|
||||
}
|
||||
next(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
*/
|
||||
|
||||
package simapi.middlewares
|
||||
|
||||
import soulsoft_web_http.*
|
||||
import soulsoft_extensions_logging.*
|
||||
import simapi.communications.*
|
||||
import simapi.exceptions.*
|
||||
import simapi.configurations.*
|
||||
|
||||
/**
|
||||
* 异常处理中间件:全异常捕获,统一输出 HTTP 200 + JSON 响应。
|
||||
* 对应 C# 的 SimApi.Middlewares.SimApiExceptionMiddleware。
|
||||
*/
|
||||
public class SimApiExceptionMiddleware <: IMiddleware {
|
||||
private let _options: SimApiOptions
|
||||
private let _logger: ILogger
|
||||
|
||||
public init(options: SimApiOptions, loggerFactory: ILoggerFactory) {
|
||||
_options = options
|
||||
_logger = loggerFactory.createLogger("SimApi.ExceptionMiddleware")
|
||||
}
|
||||
|
||||
/**
|
||||
* 中间件入口(IMiddleware 风格,对齐 C# UseMiddleware<T> 的 InvokeAsync)。
|
||||
*/
|
||||
public func invoke(context: HttpContext, next: RequestDelegate): Unit {
|
||||
try {
|
||||
// 透传 Query-Id 请求头
|
||||
if (let Some(queryId) <- context.request.headers.get("Query-Id")) {
|
||||
context.response.headers.add("Query-Id", queryId)
|
||||
}
|
||||
next(context)
|
||||
// 若响应未开始且状态码不在跳过列表,则视为业务错误
|
||||
if (!context.response.hasStarted) {
|
||||
let code = Int64(context.response.statusCode)
|
||||
if (!_options.simApiExceptionOptions.skipStatusCodes.contains(code)) {
|
||||
throw SimApiException(code)
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
let response = buildResponse(ex, context)
|
||||
if (!context.response.hasStarted) {
|
||||
context.response.statusCode = 200
|
||||
context.response.contentType = "application/json; charset=utf-8"
|
||||
context.response.write(responseJson(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func buildResponse(ex: Exception, context: HttpContext): SimApiBaseResponse {
|
||||
if (let simEx: SimApiException <- ex) {
|
||||
let message = if (simEx.message.isEmpty()) {
|
||||
SimApiBaseResponse.getDefaultMessage(simEx.code)
|
||||
} else {
|
||||
simEx.message
|
||||
}
|
||||
var response = SimApiBaseResponse(simEx.code, message)
|
||||
if (context.response.statusCode == 404) {
|
||||
response.message = "接口不存在"
|
||||
}
|
||||
return response
|
||||
}
|
||||
_logger.error(EventId(0), ex, ex.message)
|
||||
return SimApiBaseResponse(500)
|
||||
}
|
||||
|
||||
private func responseJson(response: SimApiBaseResponse): String {
|
||||
response.toJsonString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
*/
|
||||
|
||||
package simapi.middlewares
|
||||
|
||||
import std.collection.*
|
||||
import std.io.*
|
||||
import std.time.*
|
||||
import soulsoft_web_http.*
|
||||
import soulsoft_extensions_logging.*
|
||||
import simapi.communications.*
|
||||
import simapi.configurations.*
|
||||
|
||||
/**
|
||||
* 请求日志中间件:记录请求方法、URL、请求头、请求体、响应状态码与耗时。
|
||||
* 对应 C# 的 SimApi.Middlewares.SimApiRequestLogMiddleware。
|
||||
*/
|
||||
public class SimApiRequestLogMiddleware <: IMiddleware {
|
||||
private let _options: SimApiOptions
|
||||
private let _logger: ILogger
|
||||
|
||||
public init(options: SimApiOptions, loggerFactory: ILoggerFactory) {
|
||||
_options = options
|
||||
_logger = loggerFactory.createLogger("SimApi.RequestLog")
|
||||
}
|
||||
|
||||
/**
|
||||
* 中间件入口(IMiddleware 风格,对齐 C# UseMiddleware<T> 的 InvokeAsync)。
|
||||
*/
|
||||
public func invoke(context: HttpContext, next: RequestDelegate): Unit {
|
||||
let start = MonoTime.now()
|
||||
let fullUrl = context.request.getDisplayUrl()
|
||||
var sb = StringBuilder()
|
||||
|
||||
sb.append("[${context.request.method}] ${fullUrl}\n")
|
||||
|
||||
// 请求头
|
||||
if (_options.simApiRequestLogOptions.showFullHeader) {
|
||||
sb.append("*( RequestHeaders [Full] ) =>\n")
|
||||
sb.append(serializeHeaders(context))
|
||||
} else {
|
||||
sb.append("*( RequestHeaders ) =>\n")
|
||||
let token = context.request.headers.get("Token") ?? ""
|
||||
let queryId = context.request.headers.get("Query-Id") ?? ""
|
||||
sb.append("Token: ${token} QueryId: ${queryId}\n")
|
||||
}
|
||||
|
||||
// 请求体
|
||||
sb.append("*( RequestBody ) =>\n")
|
||||
sb.append(readRequestBody(context))
|
||||
|
||||
// 调用下一级
|
||||
next(context)
|
||||
|
||||
// 响应信息
|
||||
let elapsed = MonoTime.now() - start
|
||||
let elapsedMs = elapsed / Duration.millisecond
|
||||
sb.append("*( Response [${context.response.statusCode}] ) => ${elapsedMs}ms\n")
|
||||
|
||||
_logger.info(sb.toString())
|
||||
}
|
||||
|
||||
private func serializeHeaders(context: HttpContext): String {
|
||||
var sb = StringBuilder()
|
||||
sb.append("{")
|
||||
var first = true
|
||||
for ((name, values) in context.request.headers) {
|
||||
if (!first) { sb.append(",") }
|
||||
sb.append("\"${SimApiJson.escapeJson(name)}\":\"${SimApiJson.escapeJson(joinValues(values))}\"")
|
||||
first = false
|
||||
}
|
||||
sb.append("}\n")
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
private func joinValues(values: Collection<String>): String {
|
||||
var sb = StringBuilder()
|
||||
var first = true
|
||||
for (v in values) {
|
||||
if (!first) { sb.append(",") }
|
||||
sb.append(v)
|
||||
first = false
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
private func readRequestBody(context: HttpContext): String {
|
||||
try {
|
||||
context.request.enableBuffering()
|
||||
var buffer = Array<Byte>(4096, repeat: 0)
|
||||
var sb = StringBuilder()
|
||||
var read = context.request.body.read(buffer)
|
||||
while (read > 0) {
|
||||
sb.appendFromUtf8(buffer.slice(0, read))
|
||||
read = context.request.body.read(buffer)
|
||||
}
|
||||
let bodyText = sb.toString()
|
||||
// 重置流位置,供后续业务读取
|
||||
if (let seekable: Seekable <- context.request.body) {
|
||||
seekable.seek(SeekPosition.Begin(0))
|
||||
}
|
||||
return truncateBody(bodyText)
|
||||
} catch (_: Exception) {
|
||||
return "(读取请求体失败)\n"
|
||||
}
|
||||
}
|
||||
|
||||
private func truncateBody(body: String): String {
|
||||
if (_options.simApiRequestLogOptions.requestStringLogLength <= 0 ||
|
||||
body.size <= _options.simApiRequestLogOptions.requestStringLogLength) {
|
||||
return body + "\n"
|
||||
}
|
||||
// 简单按长度截断(不做 JSON 字段级截断,保持实现简洁)
|
||||
let chars = body.toArray()
|
||||
var sb = StringBuilder()
|
||||
for (i in 0.._options.simApiRequestLogOptions.requestStringLogLength) {
|
||||
sb.append(chars[i])
|
||||
}
|
||||
return "${sb.toString()}...(${body.size})\n"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user