Как обслуживать динамически создаваемые URL-пути с помощью Go?

Я использую реагирующий маршрутизатор и pushHtate браузера History в проекте activjs. Этот проект позволяет пользователю создать заметку, которая создает новый путь. Чтобы обслуживать сайты такого типа, мне нужно предоставить один и тот же файл HTML для каждого пути, кроме статического содержимого. Так что мой код nodejs выглядит следующим образом.

// Serve the static content
app.use('/static/css/', express.static(path.join(__dirname, '../../react-ui/build/static/css')));
app.use('/static/js/', express.static(path.join(__dirname, '../../react-ui/build/static/js')));
app.use('/static/media/', express.static(path.join(__dirname, '../../react-ui/build/static/media')));
app.use('/static/img/', express.static(path.join(__dirname, '../../react-ui/build/static/img')));
app.use('/img/', express.static(path.join(__dirname, '../../react-ui/build/img')));

// Serve the same HTML file to everything else
app.use('*', express.static(path.join(__dirname, '../../react-ui/build'))); 

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

package main

import (
    "net/http"
)

func init(){
    fs := http.FileServer(http.Dir("web"))
    http.Handle("/", fs)
    http.Handle("/static-page-1/", http.StripPrefix("/static-page-1/", fs))
    http.Handle("/static-page-2/", http.StripPrefix("/static-page-2/", fs))
    http.Handle("/static-page-3/", http.StripPrefix("/static-page-3/", fs))
}

Можно ли предоставлять контент динамически сгенерированным URL-путям с помощью сервера Go?

Если бы метод Handle поддерживал переменные, я бы написал такой код

fs := http.FileServer(http.Dir("web"))
http.Handle("/static/", fs)
http.Handle("/{unknownUserPath}", http.StripPrefix("/{unknownUserPath}", fs))

{unknownUserPath} будет любым путем, который вводит пользователь, который не находится в /static/ path.

Вот структура проекта go

Вот сервер на основе ответа @putu

package main

import (
    "net/http"
    "strings"
)

func adaptFileServer(fs http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, req *http.Request) {
        staticIndex := strings.Index(req.URL.Path, "/static/");
        imgIndex := strings.Index(req.URL.Path, "/img/");

        if staticIndex == -1 && imgIndex == -1 {
            fsHandler := http.StripPrefix(req.URL.Path, fs)
            fsHandler.ServeHTTP(w, req)
        } else {
            fs.ServeHTTP(w, req)
        }
    }
    return http.HandlerFunc(fn)
}

func init() {
    fs := http.FileServer(http.Dir("web"))
    http.Handle("/", adaptFileServer(fs))
}

2 ответа

Решение

Если вы хотите предоставить статическое содержимое с шаблоном URL /* в конкретный каталог, а затем используйте ответ, предоставленный jeevatkm.

Если вам нужна слегка настраиваемая версия, вам нужен своего рода адаптер, который сопоставляет путь URL-адреса со статическим обработчиком файлов (http.FileServer). Пример кода выглядит так:

package main

import (
    "log"
    "net/http"
    "regexp"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello world!"))
}

func adaptFileServer(fs http.Handler, mux http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, req *http.Request) {
        //Use your Path matcher here.
        //For demonstration, REGEX match is used
        //and it's probably not the most efficient.
        staticRegex := regexp.MustCompile("^/static-page-[0-9]+/")
        if matches := staticRegex.FindStringSubmatch(req.URL.Path); matches != nil {
            log.Printf("Match: %v, %v", req.URL.Path, matches[0])
            fsHandler := http.StripPrefix(matches[0], fs)
            fsHandler.ServeHTTP(w, req)
        } else if mux != nil {
            log.Printf("Doesn't match, pass to other MUX: %v", req.URL.Path)
            mux.ServeHTTP(w, req)
        } else {
            http.Error(w, "Page Not Found", http.StatusNotFound)
        }
    }
    return http.HandlerFunc(fn)
}

func init() {
    //Usual routing definition with MUX
    mux := http.NewServeMux()
    mux.HandleFunc("/hello", helloHandler)

    //"Dynamic" static file server.
    fs := http.FileServer(http.Dir("web"))
    http.Handle("/", adaptFileServer(fs, mux))
}

func main() {
    log.Fatal(http.ListenAndServe(":8080", nil))
}

В приведенном выше примере адаптера, если путь запроса соответствует определенному шаблону (/static-page-*/ в приведенном выше примере), он будет передан http.FileServer, Если не совпадает, и если указан мультиплексор, он вызовет mux.ServeHTTP, В противном случае он вернется 404 ошибка.

Если вы хотите другое правило соответствия, просто измените regex шаблон (или использовать свой собственный сопоставитель).

Замечания:
Пожалуйста, не используйте тот же экземпляр обработчика для FileServer а также mux, Например, когда вы звоните http.Handle, это использовать http.DefaultServeMux обрабатывать маршрутизацию. Если вы пройдете http.DefaultServeMux как второй аргумент adaptFileServer Вы можете закончить бесконечной рекурсией.

Прежде всего, gorilla/mux Пакет отлично подходит для поддержки динамической маршрутизации. Но это все еще не позволяет вам удалять префиксы из динамических маршрутов. Вот как вы можете заставить это работать:

fileServer := http.FileServer(http.Dir("static"))
r.PathPrefix("/user/{name}/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Figure out what the resolved prefix was.
    name := mux.Vars(r)["name"]
    prefix := fmt.Sprintf("/user/%s/", name)
    // Strip it the normal way.
    http.StripPrefix(prefix, fileServer).ServeHTTP(w, r)
})

Простой способ динамической связи сервера на файловом сервере golang следующий:

прежде всего вам нужно реализовать промежуточное ПО для распознавания динамической ссылки или ссылки запроса, например, мы генерируем файл с динамической ссылкой /some-hex-decimal что приводит к файлу

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

код следующий

func (us *urlSplitter) splitUrl(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    //============== Read requested query string
    exn := r.URL.Query().Get("dynamic-link")
    if exn == "" {
        responseJsonStatus(w, "access-file",
            "query string 'exn' not found",
            "", http.StatusBadRequest, nil)
        return
    }

    //============== Remove query string
    r.URL.Query().Del("exn")

    //============== Generate file access path
    supplier := func() (filter interface{}) {
        return bson.D{
            {"exn", exn},
        }
    }
    ifu := us.repo.FindExportationWithFilter(r.Context(), supplier).Get()

    if ifu.Data() == nil {
        responseJsonStatus(w, "access-file",
            "request file not found",
            "", http.StatusNotFound, nil)
        return
    }

    foundEx := ifu.Data().(*entitie) // stored data in cache or any other where

    if foundEx.ExportationStatus.StatusName == utils.InTemporaryRepo ||
        foundEx.ExportationStatus.StatusName == utils.FileManagementFailed {
        responseJsonStatus(w, "access-file",
            "file is not server-able",
            "", http.StatusBadRequest, nil)
    }

    //============== Call next handler to take care of request
    r2 := new(http.Request)
    *r2 = *r
    r2.URL = new(url.URL)
    *r2.URL = *r.URL
    r2.URL.Path = foundEx.ServeUrl

    next.ServeHTTP(w, r2)
})
}

теперь для использования этого промежуточного программного обеспечения мы должны связать его на http.server следующим образом

func (s *Server) Start() {
    add := fmt.Sprintf("%s:%d", s.host, s.port)

    log.GLog.Logger.Info("Starting HttpFileHandler", "fn", "httpFileServer.Start",
        "address", add, "servePath", s.servePath)

    //============== Initial Middleware
    sp := newSplitter(s.repo)
    au := newAuth(s.usCli)

    fs := http.FileServer(http.Dir(s.servePath))

    http.Handle("/", sp.splitUrl(fs)))

    err := http.ListenAndServe(add, nil)
    if err != nil {
        log.GLog.Logger.Error("Error on starting file http server",
            "fn", "httpFileServer.init",
            "err", err)
        os.Exit(1)
    }
}

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

http.FileServer хороший выбор для обслуживания статических файлов из каталога и его подкаталогов.

func main() {
  http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

  log.Println("Listening...")
  if err := http.ListenAndServe(":8080", nil); err != nil {
     log.Fatal(err)
  }
}

Он будет обслуживать любые файлы под /static/* каталог и его подкаталоги через http://localhost:8080/static/<path-to-file>,

Поэтому создайте структуру каталогов и отобразите ее с помощью одного или нескольких обработчиков файловых серверов.


РЕДАКТИРОВАТЬ:

Как и просили в комментарии. Служите статическим файлам из корня и снизу.

http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web"))))

Это означает, что файлы под web/* будет подан от корня /,

Другие вопросы по тегам