I am trying to create a framework in which I would receive requests over REST API and would wait for another service (which works over gRPC) to poll and execute the request. This is needed cause the "other" service is very deeply embedded into the network and I can't directly call it. At the same time, I would like to buffer the output of the other service back to the request origin.
Any ideas how I can share this data between 2 different asynchronous API requests? Using the file system is a way... but I was thinking can I do it better via channels or something...?
Kind of pseudo code below:
func RestHandler(payload string) (string, error){
respChan := make(chan string)
workId := placeWorkInQueue(payload)
// Start polling in the background
go pollForResult(respChan, workId)
// wait for result in the channel
var result string
select {
case result = <-respChan:
// implement your timeout logic as a another case: here
}
return result, nil
}
// This is poller for just the workId given to it.
func pollForResult(respChan chan string, workId string) {
// Do the polling for workId result
/// Write response to respChan when available
// You may have to implement a timeout to give up polling.
}
func placeWorkInQueue(s string) string {
// Place the job in queue and return a unique workId
return "unique-id"
}
Use Redis queues in both directions. The API endpoint writes request and unique id to queue, registers Go channel with unique id as key with central reader in process, and waits on Go channel.
Queue reader gets responses with id from Redis queue and sends response to appropriate Go channel.
// Response represents the response type from the
// remote service.
type Response struct {
ID string
// ... more fields as needed
}
// Request represents request to remote serivce.
type Request struct {
ID string
// ... more fields as needed
}
// enqueueRequest writes request to Redis queue.
// Implementation TBD by application.
func enqueueRequest(r *Request) error {
return nil
}
// dequeueResponse reads a response from a Redis queue.
// Implementation TBD by application.
func dequeueResponse() (*Response, error) {
return nil, nil
}
Use sync.Map to register waiting Go channels from API request handlers. The key is the unique id for the request and the value is a chan *Response.
var responseWaiters sync.Map
Run queuePump in a single goroutine to dequeue results from Redis queue and send to appropriate channel. Start the gorountine before serving HTTP requests.
func queuePump() {
for {
response, err := dequeueResponse()
if err != nil {
// handle error
}
v, ok := responseWaiters.Load(response.ID)
if ok {
c := v.(chan *Response)
c <- response
// Remove cahnel from map to ensure that pump never sends
// twice to same channel. The pump will black forever if
// this happens.
responseWaiters.Delete(response.ID)
}
}
}
The API endpoint allocates a unique id for request, registers a channel with the queue pump, enqueues the request and waits for the response.
func apiEndpoint(w http.ResponseWriter, r *http.Request) {
id := generateUniqueID()
c := make(chan *Response, 1) // capacity 1 ensures that queue pump will not block
responseWaiters.Store(id, c)
defer responseWaiters.Delete(id)
req := &Request{
ID: id,
// fill in other fields as needed
}
if err := enqueueRequest(req); err != nil {
// handle error
}
select {
case resp := <-c:
// process response
fmt.Println(resp)
case <-time.After(10 * time.Second):
// handle timeout error
}
}
Related
I am writing an HTTP server in Go, which uses the following pattern to handle API output:
func handler(w http.ResponsWriter, r *http.Request) {
defer reply(w, r, L)() //L is a Logger
//do things...
}
func reply(w http.ResponseWriter, r *http.Request, log Logger) func() {
cid := []byte{0, 0, 0, 0}
if log != nil {
rand.Read(cid)
log.Debug("[%x] %s %s", cid, r.Method, r.URL.String())
}
entry := time.Now()
return func() {
if log != nil {
defer log.Debug("[%x] elapsed %d millis", cid, time.Since(entry).Milliseconds())
}
_, err := w.Write(nil)
if err == http.ErrHijacked {
return //API is a WEBSOCKET entry point, do nothing
}
//handle common output logic for normal HTTP APIs...
}
}
The reason I do this, is that I found this comment in the standard library:
// ErrHijacked is returned by ResponseWriter.Write calls when
// the underlying connection has been hijacked using the
// Hijacker interface. A zero-byte write on a hijacked
// connection will return ErrHijacked without any other side
// effects.
ErrHijacked = errors.New("http: connection has been hijacked")
However following the Write() method, I got this comment:
// Write writes the data to the connection as part of an HTTP reply.
//
// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// ...
Write([]byte) (int, error)
My questions are:
Is it OK to use my code to safely detect if a HTTP connection is hijacked? I only want to check the connection is hijacked or not, but do NOT want it to add headers for me!
Since the ResponseWriter is an interface, I cannot click through the source code to find out how the standard library implements that method. In general, how can I drill down to the standard library (or any open source code) to find out the implementation of an interface?
Thanks to Cerise, I found the source code of the standard response.Writer:
func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
if w.conn.hijacked() {
if lenData > 0 {
caller := relevantCaller()
w.conn.server.logf("http: response.Write on hijacked connection from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
}
return 0, ErrHijacked
}
... ....
So, as said in the document, there is NO side effect.
I have a function which may or may not be called as an asynchronous go-routine.
func APICall(request *HTTPRequest) *HTTPResponse
*HTTPRequest is a pointer to a struct which contains various pieces of data required in order to build a request:
type HTTPRequest struct {
// Represents a request to the twitter API
method string
baseurl string
urlParams map[string]string
bodyParams map[string]string
authParams map[string]string
responseChan chan *HTTPResponse
}
If called as a goroutine, i.e a channel is passed in; we build the request and write the response into the *HTTPResponse object (also a struct) of the provided channel. What is the most graceful / idiomatic way to accept a call to the function without a channel (ie. Not async)
At the moment, we do something like this within the body of APICall to deal with both kinds of function call:
if request.responseChan != nil { // If a response channel has been specified, write to that channel
request.responseChan <- &twitterHTTPResponse{body, nil}
return nil // Not returning a struct
} else {
return &twitterHTTPResponse{body, nil} // Return a pointer to a new struct representing the response
}
Are we along the right lines?
The idiomatic approach is to provide a synchronous API:
type HTTPRequest struct {
// Represents a request to the twitter API
method string
baseurl string
urlParams map[string]string
bodyParams map[string]string
authParams map[string]string
}
func APICall(request *HTTPRequest) *HTTPResponse {
...
return &twitterHTTPResponse{body, nil}
}
The caller an can easily create a goroutine if it needs to run the call concurrently. For example:
r := make(chan *HTTPResponse)
go func() {
r <- APICall(req)
}()
... do some other work
resp := <- r
Synchronous APIs are idiomatic for a couple of reasons:
Synchronous APIs are easier to use and understand.
Synchronous APIs don't make incorrect assumptions about how the application is managing concurrency. For example, the application may want to use a wait group to wait for completion instead of receiving on a channel as assumed by the API.
I have a basic HTTP server that accepts a request and returns data from a data store.
Each HTTP request does the following things:
Create a context with timeout
Create a read request (custom type)
Push read request onto channel
Wait for response and serve data
Here's the basic pseudo code:
package main
import (
"context"
"net/http"
"time"
)
type dataRequest struct {
data chan string
ctx context.Context
}
func handler(reqStream chan dataRequest) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
req := dataRequest{
data: make(chan string),
ctx: ctx,
}
select {
case reqStream <- req:
// request pushed to que
case <-ctx.Done():
// don't push onto reqStream if ctx done
}
select {
case <-ctx.Done():
// don't try and serve content if ctx done
case data := <-req.data:
// return data to client
}
}
}
func main() {
dataReqs := make(chan dataRequest)
go func() {
for {
select {
case req := <-dataReqs:
select {
case <-req.ctx.Done():
// don't push onto data channel if ctx done
case req.data <- "some data":
// get data from store
}
}
}
}()
http.HandleFunc("/", handler(dataReqs))
http.ListenAndServe(":8080", nil)
}
My question is, because the context could finish at any time due to the deadline being exceeded or the client cancelling the request, is my current approach correct for handling this in multiple places or is there a more elegant solution?
seems to me that it'll work.
few comments -
you can return in the first case of <- ctx.Done()
you're already waiting for req.ctx.Done() in the data store handler so you can completely remove the first select {} statement and just publish to the data requests channel. not sure about performance hits for the rare cases when the context is done so early before the request is even published...
Is there a way to get the connection information on RPC calls from server side? Or maybe something like unique client ID?
There is no connecton information which may help distinguish clients. One reason of this is proxies: different clients can have same IP and port (as I understand)
One possible solution is handshake protocol in app level. You can add rpc method "Connect" and send clientId as response from server. Afterthat you can attach custom headers (metadata) to your rpc calls.
Client side java code:
String clientId = getIdfromServer();
Metadata.Key<String> CLIENT_ID = Metadata.Key.of("client_id", ASCII_STRING_MARSHALLER);
Metadata fixedHeaders = new Metadata();
fixedHeaders.put(CLIENT_ID, clientId);
blockingStub = MetadataUtils.attachHeaders(blockingStub, fixedHeaders);
This C++ server side code shows how to handle such header on server:
::grpc::Status YourRPC(::grpc::ServerContext* context, const Your* request, YourResponse* response)
{
const auto clientMetadata = context->client_metadata();
auto it = clientMetadata.find("client_id");
auto clientId = std::string(it->second.begin(), it->second.end());
}
I noticed that metadata key is case insensitive. Grpc converts keys to lowercase.
gRPC now provide peer information (https://github.com/grpc/grpc-go/issues/334)
import (
"google.golang.org/grpc/peer"
)
func (s *server) Hello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
//p includes connection information
p, ok := peer.FromContext(ctx)
....
....
}
Yes , we can get the request information , connection information and etc.
There are generally two types of information we can get from the client request on grpc server side.
Method Information : We know that rpc call is simple method call . To get the method name (ie : which method will be invoked in grpc server when client will request?). below code will work.
import (
"google.golang.org/grpc"
)
func getMethodInfo(ctx context.Context) {
methodName := grpc.Method(ctx)
fmt.Println(methodName)
}
//outputex: /Abc
2.Peer Information:
p, ok := peer.FromContext(ctx)
Hope this will work.
For gRPC streaming
We can get the connection information below through google.golang.org/grpc/peer
// protobuf
service Server {
rpc Stream (stream GrpcStreamRequest) returns (stream GrpcStreamResponse) {}
}
func (ss *StreamServer) Stream(svr pb.Server_StreamServer) error {
for {
req, err := svr.Recv()
if err != nil {
fmt.Printf("Stream recv err %+v", err)
return err
}
p, ok := peer.FromContext(svr.Context())
if ok {
fmt.Printf("Client info %+v", p)
}
}
i'm new to socket and trying to create a connection pooling over tcp socket. my implementation send 32bit length then binary message for each call. But i'm having problem with sometimes the reader receiving previous response from server (could happened when client close and re-establish socket on send error). how do i flush socket (remaining bytes from previous call) before a new request. any suggestion?
Edit: i learned that tcp always stream 0s, what if i send byte(1) before message so i can have a flush function to check if socket not empty before a new call.
Your post actually asks several questions:
How to manage a connection pool?
How to handle communication over the sockets?
These are really two different things. A connection pool is just a way to manage a set of connections. A simple way to implement this is with a class such as:
package netpool
import (
"net"
)
const MaxConnections = 3
type Error string
func (e Error) Error() string {
return string(e)
}
var ErrMaxConn = Error("Maximum connections reached")
type Netpool struct {
name string
conns int
free []net.Conn
}
func NewNetpool(name string) *Netpool {
return &Netpool{
name: name,
}
}
func (n *Netpool) Open() (conn net.Conn, err error) {
if n.conns >= MaxConnections && len(n.free) == 0 {
return nil, ErrMaxConn
}
if len(n.free) > 0 {
// return the first free connection in the pool
conn = n.free[0]
n.free = n.free[1:]
} else {
addr, err := net.ResolveTCPAddr("tcp", n.name)
if err != nil {
return nil, err
}
conn, err = net.DialTCP("tcp", nil, addr)
if err != nil {
return nil, err
}
n.conns += 1
}
return conn, err
}
func (n *Netpool) Close(conn net.Conn) error {
n.free = append(n.free, conn)
return nil
}
I have created a stand-alone class here. It would typically be implemented as part of a higher-level class such as MyHTTPHost, or MyDatabase.
In this simple implementation, connections that are returned via netpool.Open() are not tracked. It's possible to leak connections by calling Open(), then closing the connections outside of netpool.Close(). It's possible to track them if you want to hold an active and inactive pool, for example, which would solve this problem.
A couple of other things you might want to add to a pooling implementation:
Threading protection (using sync.Mutex, for example)
Closing of connections in the freepool after some length of inactivity
Error checking to be sure that closed connections are still valid
Once you have a connection, you can call Read and Write on it normally. To flush all oustanding data on the socket, you can simply use the ioutil.ReadAll() helper function. By default, this will block indefinitely if there is no data available. To avoid that, add a read timeout using:
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
_, err = ioutil.ReadAll(conn)
neterr, ok := err.(net.Error)
if ok && neterr.Timeout() {
err = nil // timeout isn't an error in this case
}
if err != nil {
// handle the error case.
}
This will read all the data from the given connection if any is pending, or will return after 500ms with an I/O Timeout error if no data was pending.
The type assertion is required because ioutil.ReadAll() returns an Error interface, rather than a net.Error interface, and we need the latter to be able to easily find out if the call returned due to a timeout.