JAVA socket写http网络请求+手写springboot

706 阅读2分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

先看效果

image.png

 开发环境:
     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 大功告成?

image.png

面向对象可不止OOP,目前有兼容WebSocket的接口、有兼容spring切面、拦截器的接口。虽然目前http是调通了,但我相信未来不只是新增其他功能代码,包的分配、项目架构也会不断地变化

面向对象才刚刚开始。