这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
先看效果
开发环境:
java15,向下兼容java8
kotlin 1.4.32
java同学可自行用工具转换kt代码
复制代码
1. 搭建网络通信
1.1. 一个“短平快”的线程池,用于接受处理相应
val newCachedThreadPool = ThreadPoolExecutor(
0, Int.MAX_VALUE,
60L, TimeUnit.SECONDS,
SynchronousQueue()
)
复制代码
1.2. 创建主方法,让消息能够被服务接收
fun main() {
val ip = 8080
val server = ServerSocket(ip)
CompomentScan.execute()
HttpRouter.init()
HttpRouter.RouteMap.forEach { println("URI: ${it.key}\t${it.value.second}") }
println("开启成功:$ip")
while (true) {
val clientSocket: Socket = server.accept()
newCachedThreadPool.execute {
println("收到请求")
val inputStream = clientSocket.getInputStream()
val requestStr = String(inputStream.readNBytes(inputStream.available()))
println(requestStr)
//根据RFC2616自定义请求类
val request = HttpRequest(requestStr)
//具体实现代码
doOperate(clientSocket, request)
inputStream.close()
clientSocket.close()
println("关闭会话\n")
}
}
}
复制代码
1.3. 封装请求类
class HttpRequest(string: String) : Request {
val requestLine: RequestLine
val requestHead: Map<String, String>
val requestBody: RequestBody?
init {
val split = string.split("\n")
val requestLine = split[0]
this.requestLine = RequestLine(requestLine)
requestHead = mutableMapOf()
var content = ""
for (index in 1 until split.size) {
if (split[index].trim().isEmpty()) {
content = split.subList(index + 1, split.size).joinToString("\n")
break
}
val kv = split[index].split(":")
requestHead[kv[0]] = kv[1].trim()
}
requestBody = RequestBody(content)
}
/**
* Method SP Request-URI SP HTTP-Version CRLF
* example: GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1
*
* Note that the absolute path cannot be empty; if none is present in the original URI, it MUST be given as "/" (the server root).
*/
class RequestLine(requestLine: String) {
val method: String
val requestURI: String
val httpVersion: String
val paramiters: Map<String, String>
init {
val split = requestLine.split(" ")
this.method = split[0]
this.requestURI = split[1].ifEmpty { "/" }
this.httpVersion = split[2].trim()
paramiters = mutableMapOf()
val split1 = requestURI.split("?")
if (split1.size > 1) {
split1[1].split("&").forEach { params ->
run {
val list = params.split("=")
paramiters[list[0]] to list[1]
}
}
}
}
override fun toString(): String =
"$method $requestURI $httpVersion"
}
class RequestBody(val content: String) {
fun <T> getClass(clazz: Class<T>): T? {
return Gson().fromJson(content, clazz)
}
override fun toString(): String {
return if (content.isEmpty()) "" else "\n\n$content"
}
}
override fun toString(): String =
"$requestLine\n${requestHead.keys.joinToString("\n") { "$it: ${requestHead[it]}" }}$requestBody"
}
复制代码
1.4. 封装具体实现代码
fun doOperate(clientSocket: Socket, request: HttpRequest) {
// TODO: 2021/8/12 当前默认识别成 http协议
val protocol = Protocol.checkProtocol(request)
doHttpOperate(clientSocket, request)
}
private fun doHttpOperate(clientSocket: Socket, httpRequest: HttpRequest) {
val result = HttpRouter.invoke(httpRequest)
doHttpResponse(clientSocket, result)
}
private fun doHttpResponse(clientSocket: Socket, result: Any) {
val out = BufferedWriter(OutputStreamWriter(clientSocket.getOutputStream()))
val string = HttpResponse(result).toString()
println("发送返回")
println(string)
out.write(string)
out.flush()
out.close()
}
复制代码
2. 组件扫描
2.1. 自定义注解
2.1.1 Compoment 在这个注解下的所有类都会被我代理生成
@MustBeDocumented
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Compoment
复制代码
2.1.2 RequestMapping 在这个注解下的类且拥有这个注解的方法,我会把它加入到我的Http路由中
@Compoment
@MustBeDocumented
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class RequestMapping(
val path: String = "/",
val method: HttpRequestMethod = HttpRequestMethod.GET,
val type: HttpRequestType = HttpRequestType.FORM,
)
复制代码
2.1.3 创建测试类
@RequestMapping
class TestController {
@RequestMapping
fun index(): String {
return "测试根路径:${Date().toString()}"
}
@RequestMapping("/t1")
fun index_1(): Any {
return a("测试t1路径:${Date().toString()}")
}
}
class a(val string: String)
复制代码
2.2. 扫描所有类,根据注解进行相关配置
object CompomentScan {
//记录所有类
private val AllClass = mutableSetOf<String>()
//递归参数需要
private var currPackage = ""
//扫描方法入口
fun execute() {
findAllClass("")
}
/**
* 递归扫描
* META-INF的不要
* 外来jar包的不要
* 打印除此之外的情况,如果有,则好去进行相关配置
*/
private fun findAllClass(path: String) {
if (path.endsWith(".class")) {
val classFullName = "$currPackage.${path.substring(0, path.indexOf(".class"))}"
AllClass += classFullName
initCompoment(classFullName)
}
val resources = Thread.currentThread().contextClassLoader.getResources(path.replace('.', '/'))
while (resources.hasMoreElements()) {
val element = resources.nextElement()
when (element.protocol) {
"file" -> {
if (path == "META-INF") break
// println("file $path")
File(element.path).list()?.forEach {
currPackage = path
findAllClass(it)
}
}
"jar" -> {
// println("jar $path")
}
else -> {
println("else $path")
}
}
}
}
/**
* 初始化组件
* 在此增强类本类
*/
private fun initCompoment(classFullName: String) {
val clazz = Class.forName(classFullName)
if (clazz.annotations.isEmpty()) return
val annoSet = mutableSetOf<Annotation>()
clazz.annotations.forEach {
recursionAnnotatnions(it, annoSet)
}
annoSet.forEach {
when (it.annotationClass.qualifiedName) {
Compoment::class.java.name -> {
//此处生成代理类
ZnCompoments.proxyClass[classFullName] = clazz.getDeclaredConstructor().newInstance()
}
RequestMapping::class.java.name -> {
//此处加入到Http路由控制器。下一步通过httpRequestController生成路由器
ZnCompoments.httpRequestController[clazz] = it as RequestMapping
}
// TODO: 2021/8/14 此处可配置AOP
else -> {
}
}
}
}
//递归出所有类上的注解,排除用不到的基本项也防止栈溢出
private fun recursionAnnotatnions(annotation: Annotation, annoSet: Set<Annotation>) {
if (
annotation.annotationClass.qualifiedName == "kotlin.Metadata" ||
annotation.annotationClass.qualifiedName == "kotlin.annotation.Target" ||
annotation.annotationClass.qualifiedName == "kotlin.annotation.Retention" ||
annotation.annotationClass.qualifiedName == "kotlin.annotation.MustBeDocumented" ||
annotation.annotationClass.qualifiedName == "java.lang.annotation.Documented" ||
annotation.annotationClass.qualifiedName == "java.lang.annotation.Target" ||
annotation.annotationClass.qualifiedName == "java.lang.annotation.Retention"
) {
return
}
(annoSet as HashSet).add(annotation)
annotation.annotationClass::java.get().annotations.forEach {
annoSet.add(annotation)
if (it.annotationClass::java.get().annotations.isNotEmpty()) {
recursionAnnotatnions(it, annoSet)
}
}
}
}
复制代码
2.3. 生成路由器(路由规则)
object HttpRouter {
/**
* k: path
* v: class, method
*/
val RouteMap = mutableMapOf<String, Pair<Any, Method>>()
fun init() {
ZnCompoments.httpRequestController.forEach { (clazz, requestMapping) ->
clazz.methods.forEach { method ->
if (method.annotations.isEmpty()) return
method.annotations.forEach {
if (it.annotationClass.qualifiedName == RequestMapping::class.java.name) {
val classPath =
if (requestMapping.path == "/") "" else if (requestMapping.path.startsWith("/")) requestMapping.path else "/$requestMapping.path"
val methodPath1 = (it as RequestMapping).path
val methodPath = if (methodPath1.startsWith("/")) methodPath1 else "/$methodPath1"
RouteMap["$classPath$methodPath"] = ZnCompoments.proxyClass[clazz.name]!! to method
}
}
}
}
}
//从路由器中获取接口,并调用接口返回结果出去
fun invoke(httpRequest: HttpRequest): Any {
val pair = RouteMap[httpRequest.requestLine.requestURI]
if (pair == null) {
// TODO: 2021/8/14 404 Page
}
// TODO: 2021/8/14 此处可配置拦截器
// TODO: 2021/8/14 获取参数列表,调带参实现
return pair!!.second.invoke(pair.first)
}
}
复制代码
3. 大功告成
3.1 大功告成!
到了这个时候,就可以调用和返回数据了,http就通了。就剩下todo项后续完成,一个基本的框架就出来了。
Http规范参考了RFC2616,有兴趣的可以看一看。RFC2616
3.2 大功告成?
面向对象可不止OOP,目前有兼容WebSocket的接口、有兼容spring切面、拦截器的接口。虽然目前http是调通了,但我相信未来不只是新增其他功能代码,包的分配、项目架构也会不断地变化
面向对象才刚刚开始。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END