@mescalito90

Как шарить экземпляр класса между http хендлерами используя контекст?

Всем доброго времени!
Начал изучать GO после PHP и столкнулся с проблеммой.

Допустим есть никий сервис "Profiler" который используется для хранения и изменения некой абстрактной метрики "counter" в рамках одного запроса. При двух одновременных запросах, необходимо держать два разных экземпляра сервиса "Profiler" (для каждого запроса - свой экземпляр). После гугления я пришел к выводу что необходимо использовать context запроса, что и попытался сделать. Я создал middleware в котором создаю и инициализирую "Profiler" и сохраняю указатель на него в контекст запроса. Далее в конкретнорм хендлере я пытаюсь извлечь "Profiler" и использовать его метод. Но компилятор GO не дает мне этого сдлеать, и выдает ошибку:

/main.go:36: profiler.GetCounter undefined (type interface {} is interface with no methods)


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

Здесь привел пример кода
package main

import (
	"net/http"
	"fmt"

	"github.com/gorilla/mux"
	"github.com/gorilla/context"
)

// Contains logic for serving and modifying of metric per request
type Profiler struct{
	counter int
}

// Returns some abstract metric called "counter"
func (profiler *Profiler) GetCounter() int {
	return profiler.counter
}

// Increment some abstract metric called "counter"
func (profiler *Profiler) IncrementCounter() {
	profiler.counter++
}

func main() {
	r := mux.NewRouter()
	r.
		HandleFunc("/api/v1/foo", func (
			w http.ResponseWriter,
			r *http.Request,
		) {
			// Trying to get "SomeService" from context and use it's function
			profiler := context.Get(r, "profiler")
			c := profiler.GetCounter() // <-- Error occurs here, but I don't know why :(

			fmt.Println(c)
			// TODO: here will be another logic with using of profiler
		}).
		Methods("GET")

	// I'm planning to put here all of my services' initializations
	http.
		Handle("/", func(h http.Handler) http.Handler {
			return http.HandlerFunc(
				func(w http.ResponseWriter, r *http.Request) {
					// Attempting to initialize "SomeService" and pass it into the context
					context.Set(r, "profiler", &Profiler{})

					h.ServeHTTP(w, r)
				},
			)
		}(r))

	http.ListenAndServe("127.0.0.1:8082", nil)
}

  • Вопрос задан
  • 199 просмотров
Решения вопроса 2
nikonor
@nikonor
Программист go, perl
Как минимум надо приводить интерфейс как-то вот так.

c := profiler.(*Profiler).GetCounter()

Но тут есть еще несколько тонких моментов:
1) если нужны копии, то почему в контекс кладется адрес, а не копия? (я не запускал, просто заметил)
2) надо бы лочить каунтер при изменении
Ответ написан
@mantyr
Пишу много Golang кода с удовольствием:)
В https://golang.org/pkg/net/http/#Handler находим интерфейс Handler:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}


Создаём реализацию:
package handlers

import (
        "project/context"
)

type AddItemsRequestData struct {
        Name string `json:"name"`
}

type AddItems {
        ctx *context.Context
}

func NewAddItems(ctx *context.Context) (*AddItems, error) {
        return &AddItems{
                ctx: ctx,
        }, nil
}

func (h *AddItems) ServeHTTP(res http.ResponseWriter, req *http.Request) {
        ...
        h.ctx.Profiler.Get("name")   <-- здесь для каждого запроса пользователя вызывается метод Get() заранее инициализированного объекта
        ...
}


package main

import (
        "log"

        "project/context"
        "project/handlers"
)

func main() {
        ...
        ctx, err := context.New()
        if err != nil {
                log.Fatalf(err)
        }
        ...
        http.Handle("/profiler/", CheckHandler(handlers.NewAddItem(ctx)))
        ...
        err := http.ListenAndServe(":80", nil)
        log.Fatalf(err)
}

// CheckHandler проверяет создание хандлера
func CheckHandler(handler http.Handler, err error) http.Handler {
        if err != nil {
                log.Fatalf("handler error: %v", err)
        }
        if handler == nil {
                log.Fatalf("empty handler")
        }
        return handler
}


Это если нужно передать данные в хандлер на этапе инициализации. Если же при каждом запросе нужно что-то инициализировать - то это можно сделать в самом хандлере. Например:

package handlers

import (
        "project/context"
        "project/profiler"
)

type AddItemsRequestData struct {
        Name string `json:"name"`
}

type AddItems {
        ctx *context.Context
}

func NewAddItems(ctx *context.Context) (*AddItems, error) {
        return &AddItems{
                ctx: ctx,
        }, nil
}

func (h *AddItems) ServeHTTP(res http.ResponseWriter, req *http.Request) {
        ...
        pr := profiler.New() <-- здесь для каждого запроса пользователя создаётся новый экземпляр
        pr.SetName("name")
        ...
}


В качестве project/context используется обычный пакет со структурой в которой хранятся любые объекты. Например:
package context

import (
        "project/profiler"
)

// Context содержит необходимые библиотеки, такие как хранилища например
type Context struct {
        Profiler *profiler.Profiler
}

// New возвращает новый Context
func New(
        profiler *profiler.Profiler
) (
        *Context,
) {
        context := &Context{
                Profiler: profiler,
        }
        return context
}
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы