Как протестировать заголовки в ReverseProxy?

#unit-testing #go #testing #reverse-proxy

#модульное тестирование #Вперед #тестирование #обратный прокси

Вопрос:

Я пытаюсь модульно протестировать следующий код:

 func(h *Handler)Forward(w http.ResponseWriter, r *http.Request) {

    url, err := url.Parse("http://test.com")
    if err != nil {
       return
    }

    reverseProxy := amp;httputil.ReverseProxy{
        Director: func(r *http.Request) {
            r.URL.Host = url.Host
            r.URL.Path = "/"
            r.URL.Scheme = url.Scheme
            r.Host = url.Host
            r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
        },
    }


    reverseProxy.ServeHTTP(w, r)
}
  

Я не могу понять, как проверить, изменяются ли заголовки функцией Director. Как мы тестируем заголовки в reverseproxy в Go?

Комментарии:

1. Очевидным способом было бы отправить запрос на ваш прокси и посмотреть, получите ли вы нужные заголовки. Что вы пробовали? С какими проблемами вы столкнулись?

2. Если целью является модульное тестирование обратного прокси, вы могли бы запустить тестовый сервер с помощью httptest package, установить его в качестве места назначения для прокси, а затем, используя обычный http-клиент, отправлять запросы на обратный прокси и сравнивать заголовки от клиента с теми, что у вас есть на сервере.

Ответ №1:

1. Внедрите внешние зависимости в тестируемый модуль

Самая большая проблема, которую я вижу прямо сейчас, заключается в том, что URL, на который вы пересылаете, жестко запрограммирован в вашей функции. Это очень затрудняет модульное тестирование. Итак, первым шагом было бы извлечь URL из функции. Не зная остальной части вашего кода, Handler кажется, что это подходящее место для этого. Упрощенный:

 type Handler struct {
    backend *url.URL
}

func NewHandler() (*Handler, error) {
    backend, err := url.Parse("http://test.com")
    if err != nil {
        return nil, err
    }
    return amp;Handler{backend}, nil
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    reverseProxy := amp;httputil.ReverseProxy{
        Director: func(r *http.Request) {
            r.URL.Host = h.backend.Host
            r.URL.Path = "/"
            r.URL.Scheme = h.backend.Scheme
            r.Host = url.Host
            r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))      
        },
    }
    reverseProxy.ServeHTTP(w, r)
}
  

Обратите внимание, что я переименовал Forward в ServeHTTP , чтобы упростить этот пример.

2. Используйте httptest для тестирования обработчика в реальном времени

Следующий шаг — провести базовый тест:

 func TestHandler(t *testing.T) {
    // 1. set-up a backend server
    // 2. set-up a reverse proxy with the handler we are testing
    // 3. call the reverse-proxy
    // 4. check that the backend server received the correct header

}
  

Давайте начнем с заполнения простых частей:

 // set-up a backend server 
backendServer := httptest.NewServer(http.DefaultServeMux)
defer backendServer.Close()

backendURL, err := url.Parse(backendServer.URL)
if err != nil {
    t.Fatal(err)
}

// set-up the reverse proxy
handler := amp;Handler{backend: backendURL} // <-- here we inject our own endpoint!
reverseProxy := httptest.NewServer(handler)
defer reverseProxy.Close()

reverseProxyURL, err := url.Parse(reverseProxy.URL)
if err != nil {
    t.Fatal(err)
}

// call the reverse proxy
res, err := http.Get(reverseProxy.URL)
if err != nil {
    t.Fatal(err)
}
// todo optional: assert properties of the response
_ = res


// check that the backend server received the correct header
// this comes next...

  

3. Передайте результаты с тестового сервера на тестовый

Теперь нам нужен способ передачи полученного заголовка в основной тест. Поскольку наши тестовые серверы могут использовать произвольные обработчики, давайте расширим настройки нашего внутреннего сервера.

 var (
    mu     sync.Mutex
    header string
)

// create a backend server that checks the incoming headers
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    defer mu.Unlock()
    header = r.Header.Get("X-Forwarded-Host")
    w.WriteHeader(http.StatusOK)
}))
defer backendServer.Close()
  

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

На этом этапе мы можем реализовать наше утверждение:

 mu.Lock()
got := header
mu.Unlock()

// check that the header has been set
want := reverseProxyURL.Host
if got != want {
    t.Errorf("GET %s gives header %s, got %s", reverseProxy.URL, want, got)
}
  

Обратите внимание, что это все равно приведет к сбою, но на этот раз из-за неправильного тестируемого кода 🙂 r.Header.Get("Host") следует заменить на r.Host .

Приложение: полный пример

 package example

import (
    "net/http"
    "net/http/httptest"
    "net/http/httputil"
    "net/url"
    "sync"
    "testing"
)

type Handler struct {
    backend *url.URL
}

func NewHandler() (*Handler, error) {
    backend, err := url.Parse("http://test.com")
    if err != nil {
        return nil, err
    }
    return amp;Handler{backend}, nil
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    reverseProxy := amp;httputil.ReverseProxy{
        Director: func(r *http.Request) {
            r.URL.Host = h.backend.Host
            r.URL.Path = "/"
            r.URL.Scheme = h.backend.Scheme
            r.Header.Set("X-Forwarded-Host", r.Host)
            r.Host = h.backend.Host
        },
    }
    reverseProxy.ServeHTTP(w, r)
}

func TestHandler(t *testing.T) {
    var (
        mu     sync.Mutex
        header string
    )

    // create a backend server that checks the incoming headers
    backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        mu.Lock()
        defer mu.Unlock()
        header = r.Header.Get("X-Forwarded-Host")
        w.WriteHeader(http.StatusOK)
    }))
    defer backendServer.Close()

    backendURL, err := url.Parse(backendServer.URL)
    if err != nil {
        t.Fatal(err)
    }

    // create a server for your reverse proxy
    handler := amp;Handler{backend: backendURL}
    reverseProxy := httptest.NewServer(handler)
    defer reverseProxy.Close()

    reverseProxyURL, err := url.Parse(reverseProxy.URL)
    if err != nil {
        t.Fatal(err)
    }

    // make a request to the reverse proxy
    res, err := http.Get(reverseProxy.URL)
    if err != nil {
        t.Fatal(err)
    }
    // todo optional: assert properties of the response
    _ = res

    mu.Lock()
    got := header
    mu.Unlock()

    // check that the header has been set
    want := reverseProxyURL.Host
    if got != want {
        t.Errorf("GET %s gives header %s, got %s", reverseProxy.URL, want, got)
    }
}