У меня есть старый сервер, с которым я общаюсь по TCP. Я написал Client
для связи. Он отлично работает через командную строку в однопоточной среде, но при обработке одновременных вызовов он, очевидно, не работает из-за единого общего TCPConn.
Как сеанс работает на этом устаревшем сервере, я должен сделать следующее шаги:
- Открыть TCPConn
- Вход (аутентификация на сервере)
- Вызов нескольких отдельных команд (ie AddUser, GroupAdd) - Их может быть много, и они
- Выйти
- Закрыть TCPConn
Несмотря на наличие идентификатора сеанса, все это должно происходить через одно и то же TCP-соединение.
Это сработало в течение многих лет, но теперь, когда сервер API становится более загруженным, если клиенты одновременно обращаются к API, связь через TCPConn нарушается. Очевидно, почему это происходит. Теперь я ищу идеальное решение, чтобы этого не произошло.
Моей первой мыслью было прозрачно создать пул соединений, как это делают драйверы SQL. Проблема здесь в том, что, я считаю, что каждый SQL Query()
, QueryRow()
, и c может работать независимо, поэтому драйвер может получить TCPConn из пула соединений, запросить, а затем вернуть TCPConn в пул . Однако в моем случае мне нужно удерживать этот TCPConn на время выполнения нескольких запросов. Из-за этого он не может быть действительно прозрачным, поскольку мне нужно вручную возвращать TCPConn обратно в пул в каждой службе, поскольку клиент не может вернуть его после каждого запроса.
Вторая мысль, которая кажется вроде брутто - это поместить все эти logi c в каждый обработчик (получить conn, создать экземпляр Client
и создать один или несколько Service
(s)). Это кажется излишним, и поскольку количество обработчиков продолжает расти, это всего лишь тонны шаблонов. Это будет иметь освобожденное соединение из пула в каждом обработчике, а также Client
и Service
в каждом обработчике, а затем возврат соединения в пул в конце.
Вот пример кода ниже .
client.go
package whatever
import (
"fmt"
"log"
"net"
"net/http"
)
// Response from Client
type Response []byte
type Client struct {
conn *net.TCPConn
username string
password string
sessionID string
}
// Open and Close the TCPConn
func (c *Client) open() error { return nil }
func (c *Client) close() error { return nil }
// Login and Logout will call open and close respectively. Login calls Run()
// with user/pass and gets back a sessionID which is set on the struct.
func (c *Client) Login() error { return nil }
func (c *Client) Logout() error { return nil }
// Run sends commands to the server over the Client.conn and returns a Repsonse
// byte slice.
func (c *Client) Run(cmd string) (Response, error) { return Response{}, nil }
service.go
// UserService provides services to act on users
type UserService struct {
client *Client
}
// AddUserGroup adds a user and then adds them to GroupA.
func (s *UserService) AddUserGroup() error {
err := s.Client.Login()
if err != nil {
return err
}
defer s.Client.Logout()
resp, err := s.Client.Run("CreateUser Bob 46 'Los Angeles'")
if err != nil {
return err
}
userID := resp[1]
cmd := fmt.Sprintf("GroupAdd %v GroupA", userID)
resp, _ := s.Client.Run(cmd)
if err != nil {
return err
}
return nil
}
this-works-command-line.go
func main() {
c := Client{username: "user", "password": "pass"}
svc := UserService{client: &c}
err := svc.AddUserGroup()
if err != nil {
log.Fatal(err)
}
}
this-does-not-work-api.go
type Server struct {
Svc *UserService
}
func (s *Server) AddUserGroupHandler() http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := s.Svc.AddUserGroup()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
})
}
func main() {
c := &Client{username: "user", password: "pass"}
svc := &UserService{client: c}
server := Server{Svc: svc}
err := svc.AddUserGroup()
if err != nil {
log.Fatal(err)
}
http.Handle("/foo", server.AddUserGroupHandler())
log.Fatal(http.ListenAndServe(":8080", nil))
}
Опять же, если эта конечная точка будет поражена несколько раз, тот же TCPConn будет использован и вызовет Login()
или что-то еще, пока обрабатывается предыдущий вызов.
Кажется, я не могу найти общий шаблон, чтобы справиться с этим. Моя последняя мысль - просто принять входящий запрос и вставить его в БД или redis, а потом попросить рабочую службу забрать его.
Возможно, я просто не использую правильную терминологию для поиска.
Любая помощь приветствуется.