Перейти к содержимому

Golang как задать handler headers

  • автор:

Тестирование HTTP хендлеров в Go

Вы пишите веб-сервис на Go и, конечно же, вы хотите тестировать ваши хендлеры. У вас используется пакет net/http, но вы не знаете как протестировать возврат корректных кодов в ваших хендлерах, правильность заголовков HTTP и насколько верно был сформирован ответ клиенту.

Давайте обсудим эти вопросы и посмотрим как можно писать более тестируемые приложения с использованием внедрения зависимостей и моков.

Стандартные хендлеры

Для начала, напишем простой тест, который будет проверят код ответа. В нашем случает это должен быть 200 ответ.

// handlers.go package handlers // http.HandleFunc("/health-check", HealthCheckHandler) func HealthCheckHandler(w http.ResponseWriter, r *http.Request) < // Очень простой хендлер проверки состояния. w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") // В будущем мы хотим сообщать сообщать о состоянии // базы данных или кеша (например Redis) выполняя // простой PING и отдавать все это в запросе io.WriteString(w, ``) > 
// handlers_test.go package handlers import ( "net/http" "testing" ) func TestHealthCheckHandler(t *testing.T) < // Создаем запрос с указанием нашего хендлера. Нам не нужно // указывать параметры, поэтому вторым аргументом передаем nil req, err := http.NewRequest("GET", "/health-check", nil) if err != nil < t.Fatal(err) >// Мы создаем ResponseRecorder(реализует интерфейс http.ResponseWriter) // и используем его для получения ответа rr := httptest.NewRecorder() handler := http.HandlerFunc(HealthCheckHandler) // Наш хендлер соответствует интерфейсу http.Handler, а значит // мы можем использовать ServeHTTP и напрямую указать // Request и ResponseRecorder handler.ServeHTTP(rr, req) // Проверяем код if status := rr.Code; status != http.StatusOK < t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) >// Проверяем тело ответа expected := `` if rr.Body.String() != expected < t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected) >> 

Как видите, Go’шные пакеты testing и httptest делают тестирование наших хендлеров очень простым. Мы подготавливаем *http.Request , a *httptest.ResponseRecorder и затем используем их для проверки работы самого хендлера(коды, тело ответа и т.д.).

Если ваш хендлер ожидает определенные параметры, то это не проблема:

 // например GET /api/projects?page=1&per_page=100 req, err := http.NewRequest("GET", "/api/projects", // url.Values это просто map[string][]string url.Values, "per_page": >) if err != nil < t.Fatal(err) >// Наш хенлер может ожидать определенный ключ для API req.Header.Set("Authorization", "Bearer abc123") // Заем вызываем handler.ServeHTTP(rr, req) как в примере выше. 

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

 // Declare it outside the anonymous function var token string handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)< // ':=' operator so we don't shadow our token variable above. // Обратите внимание, что тут нужно обязательно использовать // '=' , а не ':=' чтобы избежать затенения переменной token = GetToken(r) // Мы устанавливаем заголовок как и в примере выше w.Header().Set("Content-Type", "application/json") >) // Запускаем хендлер, проверяем коды, тело ответа и т.д. if token != expectedToken < t.Errorf("token does not match: got %v want %v", token, expectedToken) >if ctype := rr.Header().Get("Content-Type"); ctype != "application/json") < t.Errorf("content type header does not match: got %v want %v", ctype, "application/json") >

Маленький лайфхак: для строк application/json или Content-Type можно использовать константы, тогда вам не прийдется заново набирать(и ошибаться), а пользоваться автоподстановкой. Опечатка в тесте может быть очень фатальна, потому что вы будете уверенны, что все протестировано и работает правильно.

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

Заполняем context.Context в тестах

Как быть с хендлерами, которые ожидают данные из context.Context? Есть ли способ руками создать контекст и заполнить его различными данными, например типом пользователя и токеном?

Предпложим, что у вас есть кастомный хендлер который предоставляет метод ServeHTTPC(context.Context, http.ResponseWriter, *http.Request) . В Go 1.7 context.Context добавят в http.Request и это сделает жизнь значительно проще.

Для примера ниже я буду использовать Goji роутер. Он предоставляет возможность использовать хендлеры с context.Context . Тем не менее, описанный способ подойдет для большинства роутеров/фрейиворков, которые работают с context.Context .

func TestGetProjectsHandler(t *testing.T) < req, err := http.NewRequest("GET", "/api/users", nil) if err != nil < t.Fatal(err) >rr := httptest.NewRecorder() // например func GetUsersHandler(ctx context.Context, // w http.ResponseWriter, r *http.Request) goji.HandlerFunc(GetUsersHandler) // Создаем новый context.Context и заполняем его данными ctx = context.Background() ctx = context.WithValue(ctx, "app.auth.token", "abc123") ctx = context.WithValue(ctx, "app.user", &YourUser) // Указываем на контекст *http.Request и ResponseRecorder. handler.ServeHTTPC(ctx, rr, req) // Проверяем код, тело ответа и т.д. if status := rr.Code; status != http.StatusOK < t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) >// Тут мы можем проверить какие данные изменились/добавились // в нашем context.Contex if id , ok := ctx.Value("app.req.id").(string); !ok < t.Errorf("handler did not populate the request ID: got %v", id) >> 

Мокаем обращения к базе данных

Наши хендлеры используют некоторый интерфейс datastore.ProjectStore с тремя методами ( Create , Get , Delete ). Мы можем замокать этот интерфейс для наших тестов определенным образом и проверить правильно ли отдаются HTTP коды.

Если вы хотите больше узнать о использовании интерфейсов для абстракции работы с базой данных, то рекомендую прочитать статью Thoughtbot и статью от fAlex Edwards.

// handlers_test.go package handlers // Возвращает ошибки в каждом методе type badProjectStore struct < // Это конкретный тип, который реализует datastore.ProjectStore. // Мы встроили его сюда, у нас автоматически добавились необходимые // методы и теперь наш тип badProjectStore удовлетворяет // интерфейсу datastore.ProjectStore, без необходимости добавлять заглушки // каждый метод (в зависимости от теста у нас будет использоваться // только те или иные методы) *datastore.Project >func (ps *badProjectStore) CreateProject(project *datastore.Project) error < return datastore.NetworkError> func (ps *badProjectStore) GetProject(id string) (*datastore.Project, error) < return nil, datastore.NetworkError> func TestGetProjectsHandlerError(t *testing.T) < var store datastore.ProjectStore = &badProjectStore<>// Мы выполняем внедрение нашего окружения в хендлеры. // Ref: http://elithrar.github.io/article/http-handler-error-handling-revisited/ env := handlers.Env req, err := http.NewRequest("GET", "/api/projects", nil) if err != nil < t.Fatal(err) >rr := httptest.Recorder() // Handler это кастомный тип, который работает с env and a http.Handler // GetProjectsHandler обращается к GetProject и должен вернуть 500 // в случае ошибки. Handler // Мы должны проверить, что в теле JSON тоже есть упоминание ошибки. expected := []byte(``) if !bytes.Equals(rr.Body.Bytes(), expected)

Пример выше несколько переусложнен, но нужно обратить внимание на несколько ключевых моментов:

  • В нашей заглушке нет никакой работы с базой данных. Модульные тесты в пакете handlers не должны ничего знать про базу.
  • Мы создали заглушку, которая возвращает ошибку и это позволило проверить работу нашего хендлера в исключительной ситуации. Мы проверили какой код возвращается и что пишется в тело ответа.
  • Как вы могли догадаться, можно добавить «хорошую» заглушку на базе *datastore.Project и протестировать в таком виде, например выполнить кодирование/декодирование в JSON. Таким образом, мы могли бы отловить ситуации, когда внесение изменений может сломать совместимость с encoding/json .

Что дальше?

Конечно, это не исчерпывающий туториал, но после прочтения этой статьи вы будете знать с чего начинать. Если вы застряли на более сложном и комплексном примере, обратитесь к сообществу или посмотрите исходники пакетов, которые используют пакет httptest via GoDoc.

Базовый веб сервер на Go

Go — это компилируемый многопоточный язык программирования отлично подходящий для создания веб сервера. В его стандартной библиотеке имеется очень мощный пакет net/http.

package main import ( «fmt» «log» «net/http» ) func main() < http.HandleFunc("/", handler) // each request calls handler log.Fatal(http.ListenAndServe("localhost:8000", nil)) >func handler(w http.ResponseWriter, r *http.Request)

Это самый простой пример веб сервера.

Выполните команду go run filename.go и сервер начнет принимать запросы.

Функция обработчик принимает интерфейс ResponseWriter, который позволяет нам писать обратно клиенту и детали HTTP-запроса.

http.Request показывает много информации о входящем запросе:

type Request struct < // Method specifies the HTTP method (GET, POST, PUT, etc.). // For client requests an empty string means GET. Method string // URL specifies either the URI being requested (for server // requests) or the URL to access (for client requests). // // For server requests the URL is parsed from the URI // supplied on the Request-Line as stored in RequestURI. For // most requests, fields other than Path and RawQuery will be // empty. (See RFC 2616, Section 5.1.2) // // For client requests, the URL's Host specifies the server to // connect to, while the Request's Host field optionally // specifies the Host header value to send in the HTTP // request. URL *url.URL // The protocol version for incoming server requests. // // For client requests these fields are ignored. The HTTP // client code always uses either HTTP/1.1 or HTTP/2. // See the docs on Transport for details. Proto string // "HTTP/1.0" ProtoMajor int // 1 ProtoMinor int // 0 // Header contains the request header fields either received // by the server or to be sent by the client. // // If a server received a request with header lines, // // Host: example.com // accept-encoding: gzip, deflate // Accept-Language: en-us // fOO: Bar // foo: two // // then // // Header = map[string][]string< // "Accept-Encoding": , // "Accept-Language": , // "Foo": , // > // // For incoming requests, the Host header is promoted to the // Request.Host field and removed from the Header map. // // HTTP defines that header names are case-insensitive. The // request parser implements this by using CanonicalHeaderKey, // making the first character and any characters following a // hyphen uppercase and the rest lowercase. // // For client requests, certain headers such as Content-Length // and Connection are automatically written when needed and // values in Header may be ignored. See the documentation // for the Request.Write method. Header Header // Body is the request's body. // // For client requests a nil body means the request has no // body, such as a GET request. The HTTP Client's Transport // is responsible for calling the Close method. // // For server requests the Request Body is always non-nil // but will return EOF immediately when no body is present. // The Server will close the request body. The ServeHTTP // Handler does not need to. Body io.ReadCloser // GetBody defines an optional func to return a new copy of // Body. It is used for client requests when a redirect requires // reading the body more than once. Use of GetBody still // requires setting Body. // // For server requests it is unused. GetBody func() (io.ReadCloser, error) // ContentLength records the length of the associated content. // The value -1 indicates that the length is unknown. // Values >= 0 indicate that the given number of bytes may // be read from Body. // For client requests, a value of 0 with a non-nil Body is // also treated as unknown. ContentLength int64 // TransferEncoding lists the transfer encodings from outermost to // innermost. An empty list denotes the "identity" encoding. // TransferEncoding can usually be ignored; chunked encoding is // automatically added and removed as necessary when sending and // receiving requests. TransferEncoding []string // Close indicates whether to close the connection after // replying to this request (for servers) or after sending this // request and reading its response (for clients). // // For server requests, the HTTP server handles this automatically // and this field is not needed by Handlers. // // For client requests, setting this field prevents re-use of // TCP connections between requests to the same hosts, as if // Transport.DisableKeepAlives were set. Close bool // For server requests Host specifies the host on which the // URL is sought. Per RFC 2616, this is either the value of // the "Host" header or the host name given in the URL itself. // It may be of the form "host:port". For international domain // names, Host may be in Punycode or Unicode form. Use // golang.org/x/net/idna to convert it to either format if // needed. // // For client requests Host optionally overrides the Host // header to send. If empty, the Request.Write method uses // the value of URL.Host. Host may contain an international // domain name. Host string // Form contains the parsed form data, including both the URL // field's query parameters and the POST or PUT form data. // This field is only available after ParseForm is called. // The HTTP client ignores Form and uses Body instead. Form url.Values // PostForm contains the parsed form data from POST, PATCH, // or PUT body parameters. // // This field is only available after ParseForm is called. // The HTTP client ignores PostForm and uses Body instead. PostForm url.Values // MultipartForm is the parsed multipart form, including file uploads. // This field is only available after ParseMultipartForm is called. // The HTTP client ignores MultipartForm and uses Body instead. MultipartForm *multipart.Form // Trailer specifies additional headers that are sent after the request // body. // // For server requests the Trailer map initially contains only the // trailer keys, with nil values. (The client declares which trailers it // will later send.) While the handler is reading from Body, it must // not reference Trailer. After reading from Body returns EOF, Trailer // can be read again and will contain non-nil values, if they were sent // by the client. // // For client requests Trailer must be initialized to a map containing // the trailer keys to later send. The values may be nil or their final // values. The ContentLength must be 0 or -1, to send a chunked request. // After the HTTP request is sent the map values can be updated while // the request body is read. Once the body returns EOF, the caller must // not mutate Trailer. // // Few HTTP clients, servers, or proxies support HTTP trailers. Trailer Header // RemoteAddr allows HTTP servers and other software to record // the network address that sent the request, usually for // logging. This field is not filled in by ReadRequest and // has no defined format. The HTTP server in this package // sets RemoteAddr to an "IP:port" address before invoking a // handler. // This field is ignored by the HTTP client. RemoteAddr string // RequestURI is the unmodified Request-URI of the // Request-Line (RFC 2616, Section 5.1) as sent by the client // to a server. Usually the URL field should be used instead. // It is an error to set this field in an HTTP client request. RequestURI string // TLS allows HTTP servers and other software to record // information about the TLS connection on which the request // was received. This field is not filled in by ReadRequest. // The HTTP server in this package sets the field for // TLS-enabled connections before invoking a handler; // otherwise it leaves the field nil. // This field is ignored by the HTTP client. TLS *tls.ConnectionState // Cancel is an optional channel whose closure indicates that the client // request should be regarded as canceled. Not all implementations of // RoundTripper may support Cancel. // // For server requests, this field is not applicable. // // Deprecated: Use the Context and WithContext methods // instead. If a Request's Cancel field and context are both // set, it is undefined whether Cancel is respected. Cancel // Response is the redirect response which caused this request // to be created. This field is only populated during client // redirects. Response *Response // contains filtered or unexported fields >

В этом случае нас интересует r.URL, структура URL, определенная в пакете net.url:

type URL struct < Scheme string Opaque string // encoded opaque data User *Userinfo // username and password information Host string // host or host:port Path string RawPath string // encoded path hint (Go 1.5 and later only; see EscapedPath method) ForceQuery bool // append a query ('?') even if RawQuery is empty RawQuery string // encoded query values, without '?' Fragment string // fragment for references, without '#' >

r.URL.Path распечатывает запрошенный путь, поэтому — наш короткий пример веб-сервера, это простое эхо URL-адреса запроса.

Как установить несколько путей в роутинге

package main import ( "fmt" "log" "sync" ) func main() < http.HandleFunc("/", handler) http.HandleFunc("/about", aboutHandler) log.Fatal(http.ListenAndServe("localhost:8000", nil)) >func handler(w http.ResponseWriter, r *http.Request) < fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) >func aboutHandler(w http.ResponseWriter, r *http.Request) < fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) //. >

Здесь все запросы к любому URL-адресу все еще обрабатываются функцией handler(), кроме пути /count. Это связано с тем, что передача параметра шаблона, заканчивающегося / в http.HandleFunc(), приведет к совпадению всех роутов, за исключением того, что уже определено.

Доступ к ресурсам

Второй пример веб-сервера предоставляет возможность обработчикам управлять общими ресурсами в среде выполнения.

package main import ( «fmt» «log» «net/http» «sync» ) var mu sync.Mutex var count int func main() < http.HandleFunc("/", handler) http.HandleFunc("/count", counter) log.Fatal(http.ListenAndServe("localhost:8000", nil)) >func handler(w http.ResponseWriter, r *http.Request) < mu.Lock() count++ mu.Unlock() fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) >func counter(w http.ResponseWriter, r *http.Request)

В этом примере используется Mutex, поскольку внутри http.HandleFun() использует goroutines для запуска обработчиков запросов, а функция handler() увеличивает глобальную переменную в текушем пакете. Чтобы избежать состояния гонки, мы должны вызвать Mutex.Lock перед изменением его значения (то же самое происходит для counter() при печати его значения).

Вывод заголовков запроса

Простой пример, как распечать заголовки запроса

func handler(w http.ResponseWriter, r *http.Request) < fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto) for k, v := range r.Header < fmt.Fprintf(w, "Header[%q] = %q\n", k, v) >fmt.Fprintf(w, "Host = %q\n", r.Host) fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr) if err := r.ParseForm(); err != nil < log.Print(err) >for k, v := range r.Form < fmt.Fprintf(w, "Form[%q] = %q\n", k, v) >>

Открытие странице в браузере приведет к выводу:

GET / HTTP/1.1 Header["Connection"] = ["keep-alive"] Header["Accept"] = ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"] Header["Accept-Language"] = ["en-us"] Header["Accept-Encoding"] = ["gzip, deflate"] Header["Upgrade-Insecure-Requests"] = ["1"] Header["User-Agent"] = ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30"] Header["Dnt"] = ["1"] Host = "localhost:8000" RemoteAddr = "127.0.0.1:51774"

Как исправить ошибку cors в Golang?

Необходимо на сервере каким-то образом отключить cors политику, чтобы я с localhost:4200 мог достучаться на сервер, чтобы получить данные. Запрос на фронте:

console.log(this.http.get('localhost:8080/videocard/2').pipe( map( data => < console.log(data); return []; >))); return <> as any; 

Я хочу внутри остановиться и увидеть какой-нибудь результат На беке

r := mux.NewRouter() r.HandleFunc("/videocard/2", func(w http.ResponseWriter, r *http.Request) < w.Header().Set("Content-Type", "application/json") w.Write([]byte("")) >) // Use default options handler := cors.Default().Handler(r) http.ListenAndServe(":8080", handler) 

Использую пакет «github.com/rs/cors» Перепробовал много всего, но что-то делаю не так и ни разу не смогу с фронта получить запрос. Отключал корсы в сафари, тоже ничего не помогает.

Упрощаем написание HTTP обработчиков на Golang

При обработке входящего HTTP запроса требуется выполнить большое количество действий, таких как:

  • Логирование входящего HTTP запроса
  • Проверка на допустимость HTTP метода
  • Выполнение аутентификации (basic, MS AD, . )
  • Проверка валидности token (при необходимости)
  • Считывание тела (body) входящего запроса
  • Считывание заголовка (header) входящего запроса
  • Собственно обработка запроса и формирование ответа
  • Установка HSTS Strict-Transport-Security
  • Установка Content-Type для исходящего ответа (response)
  • Логирование исходящего HTTP ответа
  • Запись заголовка (header) исходящего ответа
  • Запись тела исходящего ответа
  • Обработка и логирование ошибок
  • Обработка defer recovery для восстановления после возможной panic

Большинство этих действий являются типовыми и не зависят от типа запроса и его обработки.
Для продуктивной эксплуатации на каждое действие необходимо предусмотреть обработку ошибок и логирование.

Повторять все это в каждом HTTP обработчике крайне неэффективно.

Даже если вынести весь код в отдельные подфункции, все равно получается примерно по 80-100 строк кода на каждый HTTP обработчик без учета собственно обработки запроса и формирования ответа.

Ниже описан используемый мной подход, по упрощению написание HTTP обработчиков на Golang без использования кодогенераторов и сторонних библиотек.

Этот подход был скомпонован из различных источников и рекомендаций в интернете.
У любого разработчика, естественно, есть свои устоявшиеся приемы и наработки — буду рад, если кто-то поделится своими подходами.

Подход к организации HTTP обработчиков

На следующем рисунке показана упрощенная UML диаграмма выполнения HTTP обработчика на примере некоего EchoHandler.

Идея, заложенная в предлагаемый подход, достаточно простая:

  • На верхнем уровне необходимо внедрить defer фукнцию для восстановления после паники. На UML диаграмме — это анонимная функция RecoverWrap.func1, показаная красным цветом.
  • Весь типовой код необходимо вынести в отдельный обработчик. Этот обработчик встраивается в наш HTTP handler. На UML диаграмме это функция Process — показана синим цветом.
  • Собственно код функциональной обработки запроса и формирования ответа вынесен в анонимную функцию в нашем HTTP handler. На UML диаграмме это функция EchoHandler.func1 — показана зеленым цветом.

http_handler

Пример кода

Полный код приведен в репозитории.

Ниже пример кода для HTTP обработчика EchoHandler, который «зеркалит» вход на выход.

При регистрации обработчика в роутере, регистрируется не собственно обработчик EchoHandler, а анонимная функция обработки паники (она возвращается функцией RecoverWrap), которая уже вызывает наш обработчик EchoHandler.

router.HandleFunc("/echo", service.RecoverWrap(http.HandlerFunc(service.EchoHandler))).Methods("GET")

Текст функции RecoverWrap для регистрации анонимной функции обработки паники.
После объявления defer func() запускается собственно наш обработчик EchoHandler.

func (s *Service) RecoverWrap(handlerFunc http.HandlerFunc) http.HandlerFunc < return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) < // объявляем функцию восстановления после паники defer func() < var myerr error r := recover() if r != nil < msg := "HTTP Handler recover from panic" switch t := r.(type) < case string: myerr = myerror.New("8888", msg, t) case error: myerr = myerror.WithCause("8888", msg, t) default: myerr = myerror.New("8888", msg) >// расширенное логирование ошибки в контексте HTTP s.processError(myerr, w, http.StatusInternalServerError, 0) > >() // вызываем обработчик if handlerFunc != nil < handlerFunc(w, r) >>) >

Собственно, код обработчика EchoHandler. В общем случае, он запускает функцию типовой обработки HTTP запроса Process и передается ей в качестве параметра анонимную функцию обработки.

func (s *Service) EchoHandler(w http.ResponseWriter, r *http.Request) < // Запускаем универсальный обработчик HTTP запросов _ = s.Process("POST", w, r, func(requestBuf []byte, reqID uint64) ([]byte, Header, int, error) < header := Header<>// заголовок ответа // Считаем параметры из заголовка входящего запроса и поместим их в заголовок ответа for key := range r.Header < header[key] = r.Header.Get(key) >// возвращаем буфер запроса в качестве ответа, заголовок ответа и статус return requestBuf, header, http.StatusOK, nil >) >

Функция типовой обработки HTTP запроса Process. Входные параметры функции:

  • method string — HTTP метод обработчика используется для проверки входящего HTTP запроса
  • w http.ResponseWriter, r *http.Request — стандартные переменные для обработчика
  • fn func(requestBuf []byte, reqID uint64) ([]byte, Header, int, error) — собственно функция обработки, на вход она принимает буфер входящего запроса и уникальный номер HTTP request (для целей логирования), возвращает подготовленный буфер исходящего ответа, header исходящего ответа, HTTP статус и ошибку.
func (s *Service) Process(method string, w http.ResponseWriter, r *http.Request, fn func(requestBuf []byte, reqID uint64) ([]byte, Header, int, error)) error < var myerr error // Получить уникальный номер HTTP запроса reqID := GetNextRequestID() // Логируем входящий HTTP запрос if s.logger != nil < _ = s.logger.LogHTTPInRequest(s.сtx, r, reqID) // При сбое HTTP логирования, делаем системное логирование, но работу не останавливаем mylog.PrintfDebugMsg("Logging HTTP in request: reqID", reqID) >// Проверим разрешенный метод mylog.PrintfDebugMsg("Check allowed HTTP method: reqID, request.Method, method", reqID, r.Method, method) if r.Method != method < myerr = myerror.New("8000", "HTTP method is not allowed: reqID, request.Method, method", reqID, r.Method, method) mylog.PrintfErrorInfo(myerr) return myerr >// Если включен режим аутентификации без использования JWT токена, то проверять пользователя и пароль каждый раз mylog.PrintfDebugMsg("Check authentication method: reqID, AuthType", reqID, s.cfg.AuthType) if (s.cfg.AuthType == "INTERNAL" || s.cfg.AuthType == "MSAD") && !s.cfg.UseJWT < mylog.PrintfDebugMsg("JWT is of. Need Authentication: reqID", reqID) // Считаем из заголовка HTTP Basic Authentication username, password, ok := r.BasicAuth() if !ok < myerr := myerror.New("8004", "Header 'Authorization' is not set") mylog.PrintfErrorInfo(myerr) return myerr >mylog.PrintfDebugMsg("Get Authorization header: username", username) // Выполняем аутентификацию if myerr = s.checkAuthentication(username, password); myerr != nil < mylog.PrintfErrorInfo(myerr) return myerr >> // Если используем JWT - проверим токен if s.cfg.UseJWT < mylog.PrintfDebugMsg("JWT is on. Check JSON web token: reqID", reqID) // Считаем token из requests cookies cookie, err := r.Cookie("token") if err != nil < myerr := myerror.WithCause("8005", "JWT token does not present in Cookie. You have to authorize first.", err) mylog.PrintfErrorInfo(myerr) return myerr >// Проверим JWT в token if myerr = myjwt.CheckJWTFromCookie(cookie, s.cfg.JwtKey); myerr != nil < mylog.PrintfErrorInfo(myerr) return myerr >> // Считаем тело запроса mylog.PrintfDebugMsg("Reading request body: reqID", reqID) requestBuf, err := ioutil.ReadAll(r.Body) if err != nil < myerr = myerror.WithCause("8001", "Failed to read HTTP body: reqID", err, reqID) mylog.PrintfErrorInfo(myerr) return myerr >mylog.PrintfDebugMsg("Read request body: reqID, len(body)", reqID, len(requestBuf)) // вызываем обработчик mylog.PrintfDebugMsg("Calling external function handler: reqID, function", reqID, fn) responseBuf, header, status, myerr := fn(requestBuf, reqID) if myerr != nil < mylog.PrintfErrorInfo(myerr) return myerr >// use HSTS Strict-Transport-Security if s.cfg.UseHSTS < w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains") >// Логируем ответ в файл if s.logger != nil < mylog.PrintfDebugMsg("Logging HTTP out response: reqID", reqID) _ = s.logger.LogHTTPOutResponse(s.сtx, header, responseBuf, status, reqID) // При сбое HTTP логирования, делаем системное логирование, но работу не останавливаем >// Записываем заголовок ответа mylog.PrintfDebugMsg("Set HTTP response headers: reqID", reqID) if header != nil < for key, h := range header < w.Header().Set(key, h) >> // Записываем HTTP статус ответа mylog.PrintfDebugMsg("Set HTTP response status: reqID, Status", reqID, http.StatusText(status)) w.WriteHeader(status) // Записываем тело ответа if responseBuf != nil && len(responseBuf) > 0 < mylog.PrintfDebugMsg("Writing HTTP response body: reqID, len(body)", reqID, len(responseBuf)) respWrittenLen, err := w.Write(responseBuf) if err != nil < myerr = myerror.WithCause("8002", "Failed to write HTTP repsonse: reqID", err) mylog.PrintfErrorInfo(myerr) return myerr >mylog.PrintfDebugMsg("Written HTTP response: reqID, len(body)", reqID, respWrittenLen) > return nil >

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *