Проблемы с созданием HTTPS-сервера для обслуживания двоичных файлов и других файлов

Чего я хочу достичь: сервер HTTPS, специально разработанный для доставки двоичных файлов примерно на 1000 устройств, иногда одновременно (клиенты будут получать двоичные файлы через wget, curl, загрузку браузера и т. д.).

Основные функциональные возможности:

  • клиент не сможет скачать бинарник без сертификата
  • сервер разрешит просмотр/загрузку клиентского каталога через браузер (если у клиента есть сертификат)
  • сервер оптимизирован для стабильности и безопасности, затем для скорости
  • сервер должен использовать шифры высокой безопасности и TLS1.2

Чего мне удалось достичь

package main

import (
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        http.ServeFile(w, req, "/")
    })

    log.Printf("Server running\nAccess the server via: https://localhost:9900/")
    log.Fatal(http.ListenAndServeTLS(":9900", "cert.crt", "priv.key", http.FileServer(http.Dir("/"))))
}

Теперь это работает нормально, хотя он не проверяет все функции и не очень гибкий, и почему-то я хотел сделать его более надежным в будущем, так как я хочу учиться, создавая этот проект, а также расширять его в будущем, когда я Мне интересно узнать больше о серверах.

После небольшого исследования я нашел несколько фрагментов кода на GitHub и в учебных пособиях, которые привели меня к следующему фрагменту кода:

package main

import (
    "crypto/tls"
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
        http.ServeFile(w, req, "/")

    })
    cfg := &tls.Config{
        MinVersion:               tls.VersionTLS12,
        CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
        PreferServerCipherSuites: true,
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
            tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_RSA_WITH_AES_256_CBC_SHA,
            tls.TLS_AES_256_GCM_SHA384,
        },
    }

    srv := &http.Server{
        Addr:         ":9900",
        Handler:      mux,
        TLSConfig:    cfg,
        TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
    }
    log.Printf("Server running\nAccess the server via: https://localhost:9900/")
    log.Fatal(srv.ListenAndServeTLS("cert.crt", "priv.key"), http.FileServer(http.Dir("/")))

}

Проблема в том, что когда сервер запускается и я подключаюсь к нему через браузер, мне предоставляется корневой каталог, но каждый раз, когда я нажимаю на папку, URL-адрес увеличивает адрес, но страница просто обновляется в каталоге «/».

Если быть точным:

  • Сначала я подключаюсь к серверу, и мне показывают корневой каталог, мне показывают Dir1, Dir2, Dir3.
  • Я нажимаю Dir1
  • URL-адрес изменяется с https://localhost:9900 на https://localhost:9900/Dir1
  • Но я все еще в корневом каталоге

Из того, что я могу видеть... я думаю, что где-то создаю петлю. Если кто-нибудь знает, что мне нужно сделать, чтобы сделать это функциональным, помощь будет очень признательна.

ПРИМЕЧАНИЕ Вышеприведенное поведение наблюдается в Firefox, в Chrome я получаю одну из двух ошибок в журнале ошибок сервера, в зависимости от внесенных изменений:

  • 2019/09/29 19:59:37 http: ошибка рукопожатия TLS от [::1]:53287: EOF
  • 2019/09/29 19:15:59 http: ошибка рукопожатия TLS от [::1]:50457: tls: клиент не поддерживает выбранный сертификат

person John    schedule 29.09.2019    source источник
comment
Вы всегда обслуживаете / (третий аргумент ServeFile), независимо от пути запроса. Рассматривали ли вы https://golang.org/pkg/net/http/#FileServer?   -  person Peter    schedule 29.09.2019
comment
Привет @peter, я добавил http.FileServer(http.Dir(/)) вместо http.ServeFile(w, req, /), и теперь я получаю пустое страницу в качестве ответа. Пока не понимаю, как работает http, или я что-то упускаю.   -  person John    schedule 30.09.2019
comment
Документация ясна и полна и включает несколько примеров. Я настоятельно рекомендую ознакомиться с документацией по библиотекам, которые вы используете. Например, вот http.FileServer и три его примера: https://golang.org/pkg/net/http/#FileServer   -  person Adrian    schedule 30.09.2019


Ответы (1)


В другом месте есть несколько примеров того, как это сделать (как было прокомментировано).

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

package main

import (
    "crypto/tls"
    "flag"
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/rickb777/servefiles/v3"
)

var path = flag.String("path", "..", "directory for the files tp be served")
var cert = flag.String("cert", "", "file containing the certificate (optional)")
var key = flag.String("key", "", "file containing the private key (optional)")
var port = flag.Int("port", 8080, "TCP port to listen on")
var maxAge = flag.String("maxage", "", "Maximum age of assets sent in response headers - causes client caching")
var verbose = flag.Bool("v", false, "Enable verbose messages")

func main() {
    flag.Parse()

    if *verbose {
        servefiles.Debugf = log.Printf
    }

    if (*cert != "" && *key == "") ||
        (*cert == "" && *key != "") {
        log.Fatal("Both certificate file (-cert) and private key file (-key) are required.")
    }

    h := servefiles.NewAssetHandler(*path)

    if *maxAge != "" {
        d, err := time.ParseDuration(*maxAge)
        log.Printf("MaxAge: %s %v\n", d, err)
        h = h.WithMaxAge(d)
    }

    srv := &http.Server{
        Addr:    fmt.Sprintf(":%d", *port),
        Handler: h,
    }

    if *cert != "" {
        srv.TLSConfig = &tls.Config{
            MinVersion:               tls.VersionTLS12,
            CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
            PreferServerCipherSuites: true,
            CipherSuites: []uint16{
                tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
                tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
                tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
                tls.TLS_RSA_WITH_AES_256_CBC_SHA,
                tls.TLS_AES_256_GCM_SHA384,
            },
        }
        log.Printf("Access the server via: https://localhost:%d/", *port)
        log.Fatal(srv.ListenAndServeTLS(*cert, *key))

    } else {
        log.Printf("Access the server via: http://localhost:%d/", *port)
        log.Fatal(srv.ListenAndServe())
    }
}

Исходный код https://github.com/rickb777/servefiles/blob/master/v3/webserver/example.go

person Rick-777    schedule 30.09.2019
comment
Спасибо, что действительно дали ответ, Рик-777. Иногда всего нескольких слов недостаточно для новичка в новом языке. Нужен рабочий код или более практичные ответы. Этот подход, который вы показали здесь, помогает мне лучше понять обработчики, и я не нашел его изначально. В остальном, пожалуйста, будьте более открыты для других, когда просите о помощи, мы знаем, что документация есть, но одно дело прочитать ее, а другое — заставить ее работать, когда вам нужен собственный код, а вы не являетесь опытным программистом. - person John; 06.10.2019