Привет Хабр! В этой статье я хочу рассказать, как легко написать полноценный HTTP сервер на Go, дабы у читателей не возникло проблем с поиском необходимого функционала в библиотеках. Использовать будем только функционал стандартных библиотек и оригинальный компилятор.


Сервер не будет обрабатывать множество ошибок — он создан исключительно для того, чтобы объяснить, как его вообще реализовать.


1. main()


Начнём с написания главной функции. Она будет предельно проста.


package main

// Импортируем всё, что нам может понадобиться
import (
  "fmt"
  "log"
  "net/http"
  "io/ioutil"
  "os"
)

// Наша главная функция
func main() {
  // При получени запроса к "/*", если не задано других обработчиков для данного
  // запроса, вызываем функцию "handler".
  http.HandleFunc("/", handler)

  // Ну а как-же без этого?)
  log.Println("Запускаемся. Слушаем порт 8080")

  // Сканируем запросы к порту 8080. При наличии таковых - отвечаем так, как
  // указано выше
  http.ListenAndServe(":8080", nil)
}

2. handler()


Главная функция готова, но без обработчика она не будет работать.


// Обработчик запросов. Принимает поток вывода от пакета "net/http",
// а также информацию о запроса
func handler(iWrt http.ResponseWriter, iReq *http.Request) {
  // Отправляет "Привет, мир" в ответ на запрос
  fmt.Fprintln(iWrt, "Привет, мир")
}

На данном этапе сервер уже прекрасно запускается и отвечает на любой запрос фразой "Привет, мир"


3. Чтение файлов


Ни один полноценный веб-сервер не может обойтись без чтения файлов. И наш тоже.


// Считыватель файлов. Принимает имя файла и выдаёт его содержимое
func readFile(iFileName string) string {
  // Считываем файл
  lData, err := ioutil.ReadFile(iFileName)
  var lOut string // Объявляем строчную переменную
  // Если файл существует - записываем его в переменную lOut
  if !os.IsNotExist(err) {
        lOut = string(lData)
    } else { // Иначе - отправляем стандартный текст
        lOut = "404"
    }
  return lOut // Возвращаем полученную информацию
}

Теперь правим функцию handler(), дабы дать ей возможность считывать файлы


// Обработчик запросов. Принимает поток вывода от пакета "net/http",
// а также информацию о запроса
func handler(iWrt http.ResponseWriter, iReq *http.Request) {
    var lGet = iReq.URL.Path[1:]   // Упростим напсание кода
    if lGet == "" || lGet == "/" { // Дабы разрешить "/" запросы
        lGet = "index.html"
    }

    lData := readFile(lGet) // Считываем файл

    // Отправляет файл в ответ на запрос
    fmt.Fprintln(iWrt, lData)
}

4. И напоследок


Да, сервер не умеет обрабатывать ошибку 404 так, как положено.
Да, он не умеет много чего ещё.
И да, он не умеет использовать HTTPS, хотя небольшая правка функци main это способна исправить.


Однако он уже запускается и способен выдавать файлы из своей же папки. Мало того, он защищён — не позволит обращаться к файлам выше своей директории.


Написал я эту статью (повторяюсь) просто для того, чтобы тем, кто это будет читать, не пришлось разрывать документацию в поисках одной единственной функции, как это пришлось делать мне.


Полный листинг
package main

import (
    "fmt"
        "log"
    "io/ioutil"
    "net/http"
    "os"
)

// Наша главная функция
func main() {
    // При получени запроса к "/*", если не задано других обработчиков для данного
    // запроса, вызываем функцию "handler".
    http.HandleFunc("/", handler)

    // Ну а как-же без этого?)
    log.Println("Запускаемся. Слушаем порт 8080")

    // Сканируем запросы к порту 8080. При наличии таковых - отвечаем так, как
    // указано выше
    http.ListenAndServe(":8080", nil)
}

// Обработчик запросов. Принимает поток вывода от пакета "net/http",
// а также информацию о запроса
func handler(iWrt http.ResponseWriter, iReq *http.Request) {
    var lGet = iReq.URL.Path[1:]   // Упростим напсание кода
    if lGet == "" || lGet == "/" { // Дабы разрешить "/" запросы
        lGet = "index.html"
    }

    lData := readFile(lGet) // Считываем файл

    // Отправляет файл в ответ на запрос
    fmt.Fprintln(iWrt, lData)
}

func readFile(iFileName string) string {
    // Считываем файл
    lData, err := ioutil.ReadFile(iFileName)
    var lOut string // Объявляем строчную переменную
    // Если файл существует - записываем его в переменную lOut
    if !os.IsNotExist(err) {
        lOut = string(lData)
    } else { // Иначе - отправляем стандартный текст
        lOut = "404"
    }
    return lOut // Возвращаем полученную информацию
}
Поделиться с друзьями
-->

Комментарии (9)


  1. dmx102
    28.08.2016 17:55
    +3

    Жесть


  1. ertaquo
    28.08.2016 18:20
    +2

    На эту тему есть превосходная статья Writing Web Applications из официальной документации — ничего разрывать не надо, все описано в одном месте.
    У вас, по сути, раскрыто все то же самое, что и в разделе «Introducing the net/http package (an interlude)» этой статьи, разве что добавлено чтение файла. Но позволю себе заметить несколько ошибок:
    1. Ваш код уязвим и позволяет обратиться к любому файлу на сервере.
    2. 404 отправляется как текст, а не как заголовок.
    3. Вместо fmt лучше использовать log.
    Короче говоря, ваша статья не учит ничему, а скорее дает вредные советы.


    1. handicraftsman
      28.08.2016 19:15

      Это базовая версия и в ней сразу сказано, что она не создана для полноценной работы. И да, правки будут. Наверное, ими сейчас и займусь.


  1. RomanPyr
    28.08.2016 18:36

    Лучше бы с библиотеками :)


  1. 0Ilya
    28.08.2016 19:02

    Спасибо, просто и познавательно.)


  1. stDistarik
    28.08.2016 19:10

    Скажите, а каков получился размер конечной программы?


    1. handicraftsman
      28.08.2016 19:10

      1.7K


  1. G-M-A-X
    28.08.2016 19:11

    п. 3
    > // Отправляет «Привет, мир» в ответ на запрос
    fmt.Fprintln(iWrt, lData)

    Там уже не «Привет, мир», а содержимое файла

    п. 4
    >Мало того, он защищён — не позволит обращаться к файлам выше своей директории.

    А если придет запрос /../../../../еtc/passwords? Что-то не заметил у вас такой защиты.


    1. handicraftsman
      28.08.2016 19:14

      1. Спасибо, поправлю
      2. http-библиотека сокращает /../ в путях до просто /. Так-что выйдет просто /еtc/passwords и 404