当前位置: 首页 > news >正文

使用Kotlin编写一个Http服务器

首发于Enaium的个人博客


引言

在本文中,我们将使用 Kotlin 编写一个简单的 HTTP 服务器。我们将使用 Java 的 ServerSocket 类来实现这个服务器。我们将创建一个简单的服务器,它将监听端口 8000,并在接收到请求时返回一个简单的响应。

Http 的格式

HTTP 请求和响应都是文本格式的。HTTP 请求由请求行、请求头和请求体组成。HTTP 响应由状态行、响应头和响应体组成。

具体可以到 MDN 查看。

代码实现

首先我们需要创建一个Method枚举和一个Version枚举,用于表示请求的方法和版本。

enum class Method {GET,POST,UNKNOWN;companion object {fun parse(method: String): Method =when (method) {"GET" -> GET"POST" -> POSTelse -> UNKNOWN}}
}
enum class Version {HTTP_1_1,UNKNOWN;companion object {fun parse(version: String): Version = when (version) {"HTTP/1.1" -> HTTP_1_1else -> UNKNOWN}}override fun toString(): String {return when (this) {HTTP_1_1 -> "HTTP/1.1"UNKNOWN -> "UNKNOWN"}}
}

然后我们创建一个HttpRequest类,用于表示 HTTP 请求。

data class HttpRequest(val method: Method,val path: String,val version: Version,val headers: Map<String, String>,val body: String
)

接着我们为HttpRequest类添加一个静态方法parse,用于解析 HTTP 请求。

companion object {fun parse(reader: BufferedReader): HttpRequest {}
}

parse方法中,我们首先读取请求行,然后读取请求头,最后读取请求体。

首先我们先来定义一些默认值。

var method = Method.UNKNOWN
var path = ""
var version = Version.UNKNOWN
val headers = mutableMapOf<String, String>()
var body = ""

然后我们读取请求的所有数据,不过需要注意这里不不能使用readTextreadLines,这些方法会导致Socket被关闭。

val request = StringBuilder()
var readed: String?
while (reader.readLine().also { readed = it } != null && readed!!.isNotEmpty()) {request.append(readed).append("\n")
}

接着我们解析请求的数据。

request.lines().forEachIndexed { index, line ->if (index == 0) {val splitWithSpace = line.split(" ")method = Method.parse(splitWithSpace[0])path = splitWithSpace[1]version = Version.parse(splitWithSpace[2])} else if (line.contains(": ")) {val split = line.split(": ")headers[split[0]] = split[1]} else if (line.isEmpty()) {} else {body = line}
}

首先是第一行,我们使用空格分割,然后解析请求方法、路径和版本。然后是请求头,我们使用冒号空格分割,然后解析请求头。最后是请求体,如果不为空,我们就保存请求体。

最后我们将解析的数据返回。

return HttpRequest(method,path,version,headers,body
)

接着我们创建一个HttpResponse类,用于表示 HTTP 响应。

data class HttpResponse(val version: Version = Version.HTTP_1_1,val statusCode: Int,val statusText: String,val headers: Map<String, String> = emptyMap(),val body: String = ""
)

然后我们为HttpResponse类重写toString方法,用于将响应转换为字符串。

override fun toString(): String {return """$version $statusCode $statusText${headers.map { "${it.key}: ${it.value}" }.joinToString("\n")}$body""".trimIndent()
}

接着我们创建一个Handler类型,用于处理请求。

typealias Handler = (HttpRequest) -> HttpResponse

然后我们创建一个Route类,用于表示路由。

data class Route(val method: Method, val path: String, val handler: Handler)

接着创建一个Router类,用于管理路由。

class Router {private val routes = mutableListOf<Route>()fun get(path: String, handler: Handler) {routes.add(Route(Method.GET, path, handler))}fun handle(socket: ServerSocket) {}
}

Router类中,我们定义了一个routes属性,用于保存所有的路由。然后我们定义了一个get方法,用于添加一个 GET 请求的路由。最后我们定义了一个handle方法,用于处理请求。

接着我们需要实现handle方法。

fun handle(socket: ServerSocket) {while (true) {val client = socket.accept()val reader = client.getInputStream().bufferedReader()val writer = client.getOutputStream().bufferedWriter()val httpRequest = HttpRequest.parse(reader)routes.findLast { it.method == httpRequest.method && it.path == httpRequest.path }?.let {val toString = it.handler.invoke(httpRequest).toString()writer.write(toString)writer.flush()} ?: let {writer.write(HttpResponse(Version.HTTP_1_1,404,"NotFound",headers = mapOf("Content-Type" to "text/html"),body = "<h1>404 Not Found</h1>").toString())writer.flush()}client.close()}
}

handle方法中,我们首先创建一个ServerSocket,然后进入一个无限循环。在循环中,我们首先接受一个客户端连接,然后创建一个BufferedReader和一个BufferedWriter,用于读取请求和写入响应。然后我们解析请求,然后查找路由,如果找到了路由,我们就调用路由的处理函数,然后将响应写入到客户端。如果没有找到路由,我们就返回一个 404 响应。最后我们关闭客户端连接。

最后我们创建一个Server类,用于创建服务器。

class HttpServer(port: Int) {private val serverSocket: ServerSocket = ServerSocket(port)fun start() {}
}

start方法中,我们创建一个Router,然后添加一个路由,最后调用Routerhandle方法。

val router = Router()
router.get("/") { _ ->HttpResponse(Version.HTTP_1_1, 200, "OK")
}
router.get("/hello") { _ ->HttpResponse(Version.HTTP_1_1, 200, "OK",headers = mapOf("Content-Type" to "text/html"),body = "<h1>Hello World!</h1>")
}
router.handle(serverSocket)

这样我们就完成了一个简单的 HTTP 服务器。

现在来测试一下我们的服务器。

fun main() {HttpServer(8080).start()
}

在终端中运行main函数,然后在浏览器中打开http://localhost:8080http://localhost:8080/hello,你应该能看到一个简单的页面。

总结

在本文中,我们使用 Kotlin 编写了一个简单的 HTTP 服务器。我们使用 Java 的 ServerSocket 类来实现这个服务器。我们创建了一个简单的服务器,它监听端口 8080,并在接收到请求时返回一个简单的响应。我们还创建了一个简单的路由系统,用于处理不同的请求。

完整源码

相关文章:

  • MEMS:Lecture 19 Wafer bonding package
  • Vue 3 中的状态管理:使用 reactive 函数实现组件间通信和状态管理
  • Flutter 应用加速之本地缓存管理
  • zookeeper、kakfa添加用户加密
  • k8s基础命令集合
  • Wake Lock API:保持设备唤醒的利器
  • Oracle阅读Java帮助文档
  • Pytorch-Padding Layers
  • 【定义通讯数据类型】LCM搭建系统通讯
  • 重温react-01
  • Mongodb学习
  • Nginx之HTTP模块详解
  • 【LeetCode 5.】 最长回文子串
  • 主窗体设计
  • 2023年的Top20 AI应用在近一年表现怎么样?
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • ES10 特性的完整指南
  • es6(二):字符串的扩展
  • GraphQL学习过程应该是这样的
  • java取消线程实例
  • js 实现textarea输入字数提示
  • KMP算法及优化
  • laravel5.5 视图共享数据
  • Leetcode 27 Remove Element
  • MySQL的数据类型
  • Sublime text 3 3103 注册码
  • vue--为什么data属性必须是一个函数
  • 初识MongoDB分片
  • 区块链分支循环
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 微信小程序开发问题汇总
  • 从如何停掉 Promise 链说起
  • #Z2294. 打印树的直径
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (24)(24.1) FPV和仿真的机载OSD(三)
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (创新)基于VMD-CNN-BiLSTM的电力负荷预测—代码+数据
  • (动态规划)5. 最长回文子串 java解决
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (回溯) LeetCode 131. 分割回文串
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (转)jQuery 基础
  • ***详解账号泄露:全球约1亿用户已泄露
  • .libPaths()设置包加载目录
  • .net core 连接数据库,通过数据库生成Modell
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .NET MVC第五章、模型绑定获取表单数据
  • .Net Remoting(分离服务程序实现) - Part.3
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NET/C# 项目如何优雅地设置条件编译符号?