Что такое «с нуля»?
Иногда нам говорят «не изобретать колесо». Это может быть прямо, когда мы хотим написать надежное и стабильное программное обеспечение. Мы должны полагаться на зрелые рамки вместо того, чтобы написать наши собственные рамки. Однако иногда жизнь слишком скучно, если мы не оспариваем себя. Если мы хотим узнать, как работает веб-кадр, мы должны написать его самим собой. «Я вижу, и я помню. я делаю И я понимаю. “
Хорошо, вы, возможно, хотите написать свои собственные веб-структуры «с нуля». Ну, теперь у вас есть две проблемы:-). Что такое «с нуля»? Значит ли это, что нам нужно изобретать «полупроводник». Конечно, нет. По словам того, что вы хотите узнать, вы можете «стоять на плечах гигантов». Если вы хотите изучить модуль низкого уровня, вы можете записать его, начиная с TCP или HTTP-протокола. И вы можете написать HTTP-сервер и сделать веб-каркасную среду на основе него.
Что касается этой статьи, мы полагаемся на HTTP-сервере других, потому что я просто хочу знать, как работает веб-каркас.
Что такое веб-каркас?
Веб-каркас также называется Framework Web Application. Он построен на HTTP-сервере, который создан по протоколу HTTP. Как правило, HTTP-сервер используется только для принятия запроса от клиента и отвечает на клиента. Веб-каркас обеспечивает более полезные и мощные утилиты, такие как проверка данных или аутентификация пользователя.
Например, когда вы покупаете онлайн, вы нажимаете на кнопку покупки. Ваш браузер отправит запрос на веб-сервер. Веб-сервер будет проанализировать запрос в структуру данных, которую может понять веб-каркас. Веб-каркас проверит вашу личность, добавит этот продукт или информацию о пользователе в базу данных и отправит запрос на вычесть денег с вашей кредитной карты и так далее.
Что нам нужно в основном?
Ним язык программирования
Стандартные библиотеки: Asyncdispatch
и asynchttpserver
Отказ
Почему Ним?
Ним это статически набранный язык компилируемый язык программирования. Он имеет интуитивно понятный и чистый синтаксис. NIM эффективен, выразительный и элегантный. Выберите Ним И наслаждайтесь своей жизнью!
Я люблю это из-за трех причин:
- Отступ и элегантный синтаксис
- Статический тип
- Высокая производительность
Простое использование Asynchttperver
NIM имеет встроенный асинхронный HTTP-сервер, а именно asynchttpserver.
. Вам не нужно устанавливать его, просто введите Импорт Asynchttperver
использовать его.
HTTP-сервер помогает нам передавать содержимое (например, HTML, JSON и Text) к нашему клиенту (например, браузер или CURL). Это будет просмотреть запрос в осевать
или Столы
На языке Ним, который может понять наш веб-каркас.
Давайте посмотрим на код.
Http200
это код состояния, который говорит клиенту, что все в порядке. ” Hello World “- это текст, с которым мы отвечаем клиенту. Нам также необходимо ответить на клиент с заголовками HTTP.
Теперь запустите код ниже и введите localhost: 8080
В вашем браузере вы получите Здравствуйте, мир
на экране.
# nim c -r thisfile.nim import asynchttpserver, asyncdispatch var server = newAsyncHttpServer() proc cb(req: Request) {.async.} = await req.respond(Http200, "Hello World", newHttpHeaders()) waitFor server.serve(Port(8080), cb)
Давайте посмотрим на заголовки запроса и заголовки ответа, чтобы получить больше интуиции.
Запросить заголовки
GET / HTTP/1.1 # start line Host: 127.0.0.1:8080 # request headers ......
Заголовки ответа
HTTP/1.1 200 OK # start line Content-Length: 11 # response headers
Теперь давайте расширим структуру!
Подавать статические файлы
Иногда мы хотим служить статические файлы, такие как CSS, HTML, изображения и так далее. Это мудро использовать nginx. Но для небольших приложений мы просто хотим использовать самих собой.
В NIM вам нужно только подготовить строки и отправлять строки клиенту. Если вы хотите отправить HTML-файлы, вы прочитаете его и отправите его по сокете. Если вы хотите отправить JSON Text, сначала конвертируйте/сбросьте его на строки, а затем отправьте его на сокет.
Теперь следуйте три шага:
Во-первых, судите, существует ли файл. Во-вторых, судите, есть ли у нас доступ к файлам. Наконец судите, можем ли мы открывать файлы.
Давайте посмотрим на Псевдо код . соответственно
с последующим содержанием, мы даем ответить
Proc в asynchttpserver
Отказ
Примечание: ждут, нужно использовать в функциях
import asyncdispatch, asynchttpserver, os proc serveStaticFile*(req: Request, dir, filename: string) {.async.} = # exist -> have access -> can open let path = dir / filename # whether exists file if not existsFile(path): await req.respond(Http404, "File doesn't exist.", newHttpHeaders()) # whether has access to file var filePermission = getFilePermissions(path) if fpOthersRead notin filePermission: await req.respond(Http403, "You have no access to the file.", newHttpHeaders()) # whether can open file try: let content = readFile(path) await req.respond(Http200, content, newHttpHeaders()) except IOError: await req.respond(Http404, "404 Not Found.", newHttpHeaders())
Базовый маршрут
Маршрут используется для сопоставления URL для соответствующего ProC, который отвечает клиенту с содержанием.
Например :
Этот пример демонстрирует, что веб-каркас выполняет некоторые действия, такие как Fetch Info из базы данных, вход в систему, возвращает HTML и так далее в соответствии с соответствующим методом HTTP и URL.
- Получить 127.0.0.1:80808/hello -> Отправить письмо
- Получить 127.0.0.1:8080/home -> Fetch Info из базы данных
import httpcore proc findHandler*(httpMethod: HttpMethod, path: string) = case httpMethod of HttpGet: case path of "/hello": echo "get 127.0.0.1:8080/hello" # sendEmail() of "/home": echo "get 127.0.0.1:8080/home" # fetchInfoFromDatabase() else: discard of HttpPost: case path of "/hello": echo "post 127.0.0.1:8080/hello" # login() of "/home": echo "post 127.0.0.1:8080/home" # returnHome() else: discard else: discard
Использовать хеш-карту для статического маршрута
Если клиентские запросы www.example.com/Login
Наша заявка будет смотреть в таблицу HASH, чтобы найти соответствующий обработчик. Обработчик – это proc, который обрабатывает запрос от клиента и генерирует ответ.
Быстро искать URL в хэш-таблице.
type Router* = Table[URL, procHandler]
Динамический маршрут
Иногда нам нужен динамический URL для удовлетворения наших требований. Например, мы используем только один Prochandler
Чтобы обработать операции входа в систему для разных клиентов. Таким образом, каждый клиент будет иметь разные драйверы входа в систему, как www.example.com/Login/1
С www.example.com/Login/2
и так далее.
Нам нужен корпус URL, как www.example.com/login/plowid}
поймать разные идентификаторы.
Сначала мы разделим URL с /
, повернуть www.example.com/Login/1
в @ ["www.example.com", "Логин", "1"]
Отказ Затем мы переживаем хеш-таблицу, чтобы решить, будь то узор сопоставления URL.
Давайте посмотрим на Псевдо код Отказ
let routeList = route.split("/") # iterate all URL and procHandler pairs let pathList = iterate(HandlerTable) for idx in 0 ..< pathList.len: # if match continue # www.example.com => www.example.com # login => login if pathList[idx] == routeList[idx]: continue # match {id} => 2 if routeList[idx].startsWith("{"): let key = routeList[idx] if key.len <= 2: raise newException(RouteError, "{} shouldn't be empty!") let params = key[1 ..< ^1]
Regex маршрут
Мы можем использовать Seq
Чтобы сохранить маршрут Regex.
/post (? p
=> /post521
или /post1314
и получает соответствующие параметры, такие как оставлять
или оставлять
.
type ReRouter* = seq[(URL, ProcHandler)]
Мы просто повторяем URL и Prochandler в Reroute, чтобы соответствовать URL-адресам запроса.
Давайте посмотрим на Псевдо код Отказ
# find regex route for (URL, ProcHandler) in reRouter: if path.httpMethod != URL.httpMethod: continue var m: RegexMatch # save matched params like id = 2 if URL.route.match(path.route, m): for name in m.groupNames(): pathParams[name] = m.groupFirstCapture(name, URL.route)
Печенье
HTTP Protocol отсутствует. Но мы используем простое cookie для сохранения информации о пользователе.
Cookie – заголовки HTTP. Это может нечувствительную информацию о пользователю. Когда вы хотите нести конфиденциальную информацию, убедитесь, что шифруйте его с помощью алгоритма шифрования, такие как SHA-256, SHA-512 и так далее. Используйте только cookie с https, чтобы предотвратить его от атак на средних.
Это то, как выглядит печенье. Это в форме Значение имени
пара. Разные пары разделены с запятой.
Cookie: username=flywind; age=21
Parse CookieJar.
Мы используем StringTable
Хранить разные Значение имени
Пары.
type CookieJar* = object data: StringTableRef proc parse*(cookieJar: var CookieJar, text: string) {.inline.} = var pos = 0 name, value: string while true: pos += skipWhile(text, {' ', '\t'}, pos) # name = username pos += parseUntil(text, name, '=', pos) if pos >= text.len: break inc(pos) # skip '=' # value = flywind pos += parseUntil(text, value, ';', pos) # username = flywind cookieJar[name] = move(value) if pos >= text.len: break inc(pos) # skip ';'
Также мы хотим установить cookie.
import options, times, strtabs, parseutils, strutils type SameSite* {.pure.} = enum None, Lax, Strict Cookie* = object name*, value*: string # root => admin expires*: string maxAge*: Option[int] domain*: string path*: string secure*: bool httpOnly*: bool sameSite*: SameSite
Установить cookie
proc setCookie*(cookie: Cookie): string = result.add cookie.name & & cookie.value if cookie.domain.strip.len != 0: result.add("; Domain=" & cookie.domain) if cookie.path.strip.len != 0: result.add("; Path=" & cookie.path) if cookie.maxAge.isSome: result.add("; Max-Age=" & $cookie.maxAge.get()) if cookie.expires.strip.len != 0: result.add("; Expires=" & cookie.expires) if cookie.secure: result.add("; Secure") if cookie.httpOnly: result.add("; HttpOnly") if cookie.sameSite != None: result.add("; SameSite=" & $cookie.sameSite)
Промежуточное программное обеспечение
Промежуточное программное обеспечение используется для выполнения некоторых операций до Prochandler и после Prochandler. Например, мы хотим проверить идентификацию пользователя, мы можем использовать промежуточное программное обеспечение для этой работы.
Давайте посмотрим на Псевдо код Отказ
proc verify(user: User, next: Handler) = # check whether user is valid if not user.isValid: resp 404, "You have no access to this URL." return # call next middleware or procHandler next() # If user is valid, print "Welcome" message resp "Welcome, " & $user
Существует два способа реализации промежуточного программного обеспечения, один основан на вызове функций, другой основан на крючке.
Давайте посмотрим на ход
Мы устанавливаем до
и после
Тип функции для каждого объекта промежуточного программного обеспечения.
type Middleware = object before: proc() after: proc()
В срок службы применения, предполагая, что у нас есть два подразготения. Таким образом, порядок исполнения:
m1.before -> m2.before -> Здравствуйте -> m2.after -> m1.after
Давайте посмотрим на Псевдо код Отказ
proc hello() = resp "Hello" var app = Application() var m1 = Middleware(...) var m2 = Middleware(...) app.addRoute(procHandler = hello, middlwares = [m1, m2])
Давайте посмотрим на путь функции
Мы используем Seq
хранить подразнения. жду следующего
используется для вызова следующего промежуточного программного обеспечения или Prochandler.
У нас есть Размер
переменная для получения текущего промежуточного программного обеспечения. В начале Размер
0. И мы получаем первое промежуточное программное обеспечение. Теперь мы начинаем выполнять первое промежуточное программное обеспечение. Когда мы сталкиваемся с жду следующего
Отказ Размер
увеличится на один, и мы назовем второе промежуточное программное обеспечение. Наконец мы называем последнее промежуточное программное обеспечение и выполнить программу отдыха.
M1 -> M1.Next -> M2 -> M2.Next -> Hello -> M2 -> M1
Давайте посмотрим на Псевдо код Отказ
type Middlewares* = seq[procHandler] proc httpRedirectMiddleWare*() = case request.scheme of "http": setScheme(request, "https") of "ws": setScheme(request, "wss") else: return # Will call next middleware or procHandler await next() response.code = Http307
Обработчик исключений
Мы можем сопоставить коды состояния HTTP для определенного пользовательского обработчика исключений.
Иногда мы хотим связать код состояния HTTP к единым страницам, например, пользовательские 404 страницы.
type ErrorHandlerTable* = Table[HttpCode, ErrorHandler] proc default404Handler*() = response.body = errorPage("404 Not Found!", PrologueVersion) app.errorHandlerTable.add(Http404, default404Handler)
Давайте посмотрим на Псевдо код Отказ
if response.code in app.errorHandlerTable: await (app.errorHandlerTable[response.code])()
Больше части
Что касается веб-структуры, есть больше частей, чем я упомянул, Вы можете оформить заказ
Оригинал: “https://dev.to/xflywind/write-a-simple-web-framework-in-nim-language-from-scratch-ma0”