Рубрики
Без рубрики

Напишите простую веб-каркас в языке NIM с нуля

Что такое «с нуля»? Иногда нам говорят «не изобретать колесо». Это может быть прямо, когда … Теги с веб, NIM, Framework, Python.

Что такое «с нуля»?

Иногда нам говорят «не изобретать колесо». Это может быть прямо, когда мы хотим написать надежное и стабильное программное обеспечение. Мы должны полагаться на зрелые рамки вместо того, чтобы написать наши собственные рамки. Однако иногда жизнь слишком скучно, если мы не оспариваем себя. Если мы хотим узнать, как работает веб-кадр, мы должны написать его самим собой. «Я вижу, и я помню. я делаю И я понимаю. “

Хорошо, вы, возможно, хотите написать свои собственные веб-структуры «с нуля». Ну, теперь у вас есть две проблемы:-). Что такое «с нуля»? Значит ли это, что нам нужно изобретать «полупроводник». Конечно, нет. По словам того, что вы хотите узнать, вы можете «стоять на плечах гигантов». Если вы хотите изучить модуль низкого уровня, вы можете записать его, начиная с 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 [\ d] +) => /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”