Kotlin http Server — пишем простой сервер

Привет. Сегодня мы напишем простой kotlin http server без использования сторонних библиотек. Наш сервер будет принимать get запросы и параметры из адресной строки. Для чего это нужно и почему бы не использовать другие средства? Потому что наша цель — просто just 4fun, ну и заодно разобраться в общих чертах как оно работает. Лучшим решением для того чтобы создать сервер на kotlin будут сторонние фреймворки и библиотеки, такие как — Ktor, Spring и т.д.

Kotlin Http Server — начало

В Java есть готовый класс HttpServer, который реализует простой HTTP-сервер, привязанный к IP и номеру порта и прослушивает входящие TCP-соединения от клиентов по этому адресу. Подкласс HttpsServer реализует сервер, который обрабатывает запросы HTTPS.

простой http server java

Приступим к делу. Функция simpleServer принимает на вход параметр port, где мы указываем порт, на котором будет работать наш сервер. В ней создаем контекст, то есть путь по которому мы будем получать данные от http server.

Затем добавляем заголовок «Content-type», «text/plain» и создаем OutputStream, в который передадим текст «Hello from server!». С помощью метода write передаем строку в поток. Не забываем вызвать encodeToByteArray, чтобы конвертировать string в массив байт. Методом flush отдаем данные. Запускаем при помощи HttpServer.start.

fun simpleServer(port: Int) {
    HttpServer.create(InetSocketAddress(port), 0).apply {
        createContext("/api") { http ->
            http.responseHeaders.add("Content-type", "text/plain")
            http.sendResponseHeaders(200, 0)
            val os: OutputStream = http.responseBody
            os.write("Hello from server!".encodeToByteArray())
            os.flush()
            http.close()
        }
        start()
    }
}

Если мы откроем браузер и перейдем по адресу 127.0.0.1/api, то увидим приветственную надпись — «Hello from server!»

Дополнительные методы

Все хорошо, но кроме get запроса мы можем послать post и получим ошибку. Ввиду того, что пример этот учебный, давайте ограничимся только get. Так же, неплохо было бы добавить потоков, чтобы сервак выдерживал минимальную нагрузку.

Добавляем пул потоков в количестве 10 штук

val threadPool = Executors.newFixedThreadPool(10) as ThreadPoolExecutor

Затем устанавливаем его с помощью метода setExecutor. Для наглядности, допишем вывод в консоль состояние работы.

fun simpleServer(port: Int) {
    HttpServer.create(InetSocketAddress(port), 0).apply {

        setExecutor(threadPool);
        println("Server runs at: 127.0.0.1:${address.port}")

        createContext("/api") { http ->
            when(http.requestMethod) {
                "GET" -> {
                    http.responseHeaders.add("Content-type", "text/plain")
                    http.sendResponseHeaders(200, 0)
                    val os: OutputStream = http.responseBody
                    if (http.requestURI.toString().indexOf("?") > 0) {
                        os.write(getParams(http.requestURI.toString()).toString().encodeToByteArray())
                        os.flush()
                    } else {
                        os.write("Hello from server!".encodeToByteArray())
                        os.flush()
                    }
                    http.close()
                }
                "POST" -> { http.sendResponseHeaders(405, 0) }
            }
        }
        start()
    }
}

В блоке when проверим метод запроса, и если он «GET», то выполняем код, а если «POST» — отправляем заголовок с кодом ошибки 405, то есть о том, что данный метод не поддерживается. Уже неплохо, но чего-то все равно не хватает…

HttpServer параметры из url

Как и любой сервер, даже самый примитивный, наш должен уметь получать параметры из url. Сделать это можно разными способами. Но мы пойдем по простому пути и накидаем функцию, которая будет парсить ссылку вида «/api?limt=10&page=1» и т.д.

fun getParams(paramString: String): Map<String,String> {
    val params = mutableMapOf<String,String>()

    paramString
        .substring(paramString.indexOf("?")+1, paramString.length)
        .split(Regex("&"))
        .forEach {
            params.put(it.split("=")[0],it.split("=")[1])
        }

    return params.toMap()
}

Метод getParams на вход принимает строку, которую обрезает с конца и до символа «?». Затем разбивает ее на массив по «=». Левую часть мы кладем в Map в качестве названия параметра сервера, а правую часть в виде значения.

Чтобы увидеть get-параметры в ответе, можно добавить

os.write(getParams(http.requestURI.toString()).toString().encodeToByteArray())

Пора проверить как все работает. В главном методе main запускаем simpleServer, передав в качестве аргумента номер порта

fun main(args: Array<String>) {
    simpleServer(8080)
}

В консоль выводится сообщение: Server runs at: 127.0.0.1:8080. Теперь запускаем любой браузер на ПК и переходим по адресу 127.0.0.1:8080/api где укажем два параметра: title равный hello и port со значением 8080.

простой рабочий сервер на сокетах Kotlin
простой рабочий сервер на Kotlin

В итоге, мы видим искомый ответ в браузере. Если мы передаем параметры в url, то видим их в теле страницы, если нет — то отображается наша приветственная надпись

Выводы

Целью статьи не было разобрать «по косточкам» работу HttpServer, а просто показать базовые принципы работы. К тому же, если начать писать свой «сервак», добавив:

  • все типы запросов (post, put, update…)
  • поддержку cookie
  • сессии
  • авторизацию O-Auth и многое другое

…то в конечном счете получится один из фреймворков-велосипедов. Необходимо ли это? Не уверен, ведь есть ktor, Springboot и еще много хороших инструментов. В следующих статьях мы возьмем один из них и набросаем REST API сервис с генерацией пользователей. Конечно, зальем его на heroku и протестируем.

Всем пока, и спасибо за внимание!

Оставить ответ