http server does not stop when I call listener.Close() - http

I'm trying to write a http server which when received terminate signal, it will wait until all requests has been responsed then close with code below
// start server
var wg sync.WaitGroup
server := &http.Server{
ConnState: func(conn net.Conn, state http.ConnState) {
switch state {
case http.StateNew:
wg.Add(1)
case http.StateClosed:
wg.Done()
}
},
}
go func(server *http.Server, listener net.Listener, terminate chan<- int) {
var status int
if err := server.Serve(listener); err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
log.Println(err)
status = 1
} else {
status = 0
}
} else {
status = 0
}
log.Printf("performing graceful close on all clients\n")
wg.Wait()
terminate <- status
}(server, listener, terminate)
but when I call listener.Close() it stuck at wg.Wait() even though all request has already been response. And while it wait, if I send another request, it still give a response correctly.

Related

How to make a HTTP request from server to client using grpc in golang

Problem Statement
I have a client (which dials to the server) and server (that listens for incoming requests) written in golang and with the RPC calls defined. I am trying to initiate an HTTP request on the server side which would in turn execute the RPC call for streaming and send a JSON response back to the user
Challenge
I was able to handle both grpc and HTTP requests on different ports but having issues with passing parameters from the HTTP request onto the RPC call on the server side
Server Code
log.Println("Listening for connections from client ........")
lis, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := testApi.Server{}
grpcServer := grpc.NewServer()
testApi.RegisterTestApiServiceServer(grpcServer, &s)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %s", err)
}
func main() {
go runGrpc()
log.Printf("*------ Waiting for requests from users ------*")
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/exchangeId/{test_id}", ConnectAndExchange).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", router))
}
func ConnectAndExchange(w http.ResponseWriter, r *http.Request){
vars := mux.Vars(r)
test_id, _ := strconv.Atoi(vars["test_id"])
log.Println("Test id request from user : ", test_id)
func (s * Server) ConnectAndStream(channelStream TestApiService_ConnectAndStreamServer) error {
// Question: This Id has to come from http request above- test_id
var id int32 = 1234566
// id := a.ConnectAndExchange
log.Println("Id from sam user ", id)
// var id int32 = 1234566
for i := 1; i <= 2; i++ {
id += 1
log.Println("Speed Server is sending data : ", id)
channelStream.Send(&Input{Id: id})
}
for i := 1; i <= 2; i++ {
log.Println("now time to receive")
client_response, err := channelStream.Recv()
log.Println("Response from samd client : ", client_response.Id)
if err != nil {
log.Println("Error while receiving from samd : ", err)
}
}
return nil
}
I am stuck with being able to pass the test_id from the curl request to the RPC call as above. Any input is greatly appreciated
Note
Client - Dials in and connects to the server and starts receiving and sending data (bi-directional streaming)
Both the Http and GRPC client are part of the same server application. So why call the RPC method from the Http handler? The Http handler should have access to the same backend functionality.
Your question is slightly unclear but if you are trying to have your client establish a GRPC connection to the server via the HTTP handler this will not work. The GRPC connection established in this situation is between the server and its self.
Edit - thanks for the clarification. Now I understand better the flow that you are trying to achieve. Your http handler method can make the outgoing grpc call to the server and return the response back via the http.ResponseWriter
For simplicity I have used the hello world example on https://github.com/grpc/grpc-go/tree/master/examples/helloworld
Running the code sample below and hitting http://localhost:1000/exchangeId/Test will show the output
Starting
*------ Waiting for http requests from users on port 1000 ------*
server listening at 127.0.0.1:1001
Test id request from user : Test
Server Received: Test
Greeting: Hello Test
Code sample:
import (
"context"
"log"
"net"
"net/http"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
"github.com/gorilla/mux"
)
var (
grpcserver = "localhost:1001"
)
func main() {
log.Print("Starting")
go StartGrpcServer()
log.Printf("*------ Waiting for http requests from users on port 1000 ------*")
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/exchangeId/{test_id}", ConnectAndExchange).Methods("GET")
log.Fatal(http.ListenAndServe(":1000", router))
}
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Server Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func StartGrpcServer() {
lis, err := net.Listen("tcp", grpcserver)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
func ConnectAndExchange(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
test_id := vars["test_id"]
log.Println("Test id request from user : ", test_id)
// Set up a connection to the server.
conn, err := grpc.Dial(grpcserver, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
resp, err := c.SayHello(ctx, &pb.HelloRequest{Name: test_id})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", resp.GetMessage())
w.Write([]byte(resp.GetMessage()))
}

Terminate server processing on client timeout

I would like to know if there's any way of making a Go HTTP server aware of a timeout in the client, and immediately terminate the processing of the ongoing request. Currently, I've tried setting timeouts on the client side that actually work as expected on their side and the request finishes with context deadline exceeded (Client.Timeout exceeded while awaiting headers) after the timeout is reached.
req, err := http.NewRequest(http.MethodGet, URL, nil)
if err != nil {
log.Fatal(err)
}
client := http.Client{Timeout: time.Second}
_, err = client.Do(req)
if err != nil {
log.Fatal(err)
}
I've also tried with different versions of the client code, like using a request with context, and got the same result, which is ok for the client side.
However, when it comes to detect the timeout on the server side, it turns out that the processing of the request continues until the server finishes its work, regardless of the timeout in the client, and what I would like to happen (I don't know if it's even possible) is to immediately terminate and abort the processing once the client has timed out.
The sever side code would be something like this (just for the sake of the example, in production code it would be something more sophisticated):
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Println("before sleep")
time.Sleep(3 * time.Second)
fmt.Println("after sleep")
fmt.Fprintf(w, "Done!")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
When the previous code is run, and a request hits the HTTP server, the following sequence of events occurs:
Server prints before sleep
Server falls asleep
Client times out and terminates with error context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Server wakes up and prints after sleep
But what I would like to happen is to terminate the process at step 3.
Thank being said, I'd like to know your thoughts about it, and whether you think what I want to do is feasible or not.
There are a few different ideas at play here. First, to confirm what you are asking for, it looks like you want to make a client disconnection trigger the whole server to be shut down. To do this you can do the following:
Add a context.WithCancel or a channel to use to propagate a shutdown event
Watch for a disconnect in your http handler and cancel the context
Add a goroutine that shuts down your server when the channel is closed
Here is a complete sample program that produces the following output:
go run ./main.go
2021/03/04 17:56:44 client: starting request
2021/03/04 17:56:44 server: handler started
2021/03/04 17:56:45 client: deadline exceeded
2021/03/04 17:56:45 server: client request canceled
2021/03/04 17:56:45 server: performing server shutdown
2021/03/04 17:56:45 waiting for goroutines to finish
2021/03/04 17:56:45 All exited!
// main.go
package main
import (
"context"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"sync"
"time"
)
func main() {
wg := &sync.WaitGroup{}
srvContext, srvCancel := context.WithCancel(context.Background())
defer srvCancel()
srv := http.Server{
Addr: ":8000",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("server: handler started")
select {
case <-time.After(2 * time.Second):
log.Printf("server: completed long request")
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
case <-r.Context().Done():
log.Printf("server: client request canceled")
srvCancel()
return
}
}),
}
// add a goroutine that watches for the server context to be canceled
// as a signal that it is time to stop the HTTP server.
wg.Add(1)
go func() {
defer wg.Done()
<-srvContext.Done()
log.Printf("server: performing server shutdown")
// optionally add a deadline context to avoid waiting too long
if err := srv.Shutdown(context.TODO()); err != nil {
log.Printf("server: shutdown failed with context")
}
}()
// just simulate making the request after a brief delay
wg.Add(1)
go makeClientRequest(wg)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Fprintf(os.Stderr, "Server failed listening with error: %v\n", err)
return
}
log.Printf("waiting for goroutines to finish")
wg.Wait()
log.Printf("All exited!")
}
func makeClientRequest(wg *sync.WaitGroup) {
defer wg.Done()
// delay client request
time.Sleep(500 * time.Millisecond)
log.Printf("client: starting request")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1:8000", http.NoBody)
if err != nil {
log.Fatalf("failed making client request")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Printf("client: deadline exceeded")
} else {
log.Printf("client: request error: %v", err)
}
return
}
// got a non-error response
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
log.Printf("client: got response %d %s", resp.StatusCode, string(body))
}

Detect failed http connections in go

Let's suppose client uploads heavy image by http with slow internet connection, he opens connection to server, starts to write data and suddenly he loses this connection because of network problems. How can I detect this on server if handler function was not yet called. The solution I found is to check the connection state. But the problem is that it's not scalable, because a lot of goroutines will interact with global variable. Are there any more elegant solutions?
package main
import (
"fmt"
"log"
"net"
"net/http"
)
// current state of connections
var connStates = make(map[string]http.ConnState)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("got new request")
fmt.Println(connStates)
})
srv := &http.Server{
Addr: ":8080",
ConnState: func(conn net.Conn, event http.ConnState) {
if event != http.StateClosed {
connStates[conn.RemoteAddr().String()] = event
} else {
if connStates[conn.RemoteAddr().String()] == http.StateActive {
fmt.Println("client cancelled request")
}
delete(connStates, conn.RemoteAddr().String())
}
},
}
log.Fatal(srv.ListenAndServe())
}
You could use context within your handler, for example, this would detect when the client disconnects and return and http.StatusPartialContent besides calling someCleanup() in where you could have your logging logic.
https://play.golang.org/p/5Yr_HBuyiZW
func helloWorld(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ch := make(chan struct{})
go func() {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "Hello World!")
ch <- struct{}{}
}()
select {
case <-ch:
case <-ctx.Done():
http.Error(w, ctx.Err().Error(), http.StatusPartialContent)
someCleanUP()
}
}
If you only need to have logs you can even simplify the code:
srv := &http.Server{
Addr: ":8080",
ConnState: func(conn net.Conn, event http.ConnState) {
log.Printf("addr: %s, changed state to: %s", conn.RemoteAddr(), event.String())
},
}
That callback will be triggered on each change of the conn

How can I serve SSH and HTTP(S) traffic from the same listener in Go?

I want to be able to serve both SSH and HTTPS connections on the same port. To do so, I need to examine the first few bytes sent by the client, and if it starts with "SSH", serve the connection one way, but let the Go HTTP server handle it if it isn't SSH.
But the http package will only work with a net.Listener. Once I accept a connection from the listener and examine the first bytes, it's too late to send the net.Conn to http.
How can I accomplish this?
Make two custom listeners, one for SSH connections, and one for all other connections. Then accept connections from the raw listener, peek at the first bytes, and send the connection to the appropriate listener.
l := net.Listen("tcp", ":443")
sshListener, httpListener := MuxListener(l)
go sshServer.Serve(sshListener)
go httpServer.Serve(httpListener)
MuxListener:
// MuxListener takes a net.Listener and returns two listeners, one that
// accepts connections that start with "SSH", and another that accepts
// all others. This allows SSH and HTTPS to be served from the same port.
func MuxListener(l net.Listener) (ssh net.Listener, other net.Listener) {
sshListener, otherListener := newListener(l), newListener(l)
go func() {
for {
conn, err := l.Accept()
if err != nil {
log.Println("Error accepting conn:", err)
continue
}
conn.SetReadDeadline(time.Now().Add(time.Second * 10))
bconn := bufferedConn{conn, bufio.NewReaderSize(conn, 3)}
p, err := bconn.Peek(3)
conn.SetReadDeadline(time.Time{})
if err != nil {
log.Println("Error peeking into conn:", err)
continue
}
prefix := string(p)
selectedListener := otherListener
if prefix == "SSH" {
selectedListener = sshListener
}
if selectedListener.accept != nil {
selectedListener.accept <- bconn
}
}
}()
return sshListener, otherListener
}
listener:
type listener struct {
accept chan net.Conn
net.Listener
}
func newListener(l net.Listener) *listener {
return &listener{
make(chan net.Conn),
l,
}
}
func (l *listener) Accept() (net.Conn, error) {
if l.accept == nil {
return nil, errors.New("Listener closed")
}
return <-l.accept, nil
}
func (l *listener) Close() error {
close(l.accept)
l.accept = nil
return nil
}
bufferedConn:
type bufferedConn struct {
net.Conn
r *bufio.Reader
}
func (b bufferedConn) Peek(n int) ([]byte, error) {
return b.r.Peek(n)
}
func (b bufferedConn) Read(p []byte) (int, error) {
return b.r.Read(p)
}

Gracefully shutting down multiple servers

I have an application that runs a basic HTTP server and also accepts connections over TCP.
Basic pseudo code is as follows:
package main
import (
"log"
"net"
"net/http"
)
func main() {
// create serve HTTP server.
serveSvr := http.NewServeMux()
serveSvr.HandleFunc("/", handler())
// create server error channel
svrErr := make(chan error)
// start HTTP server.
go func() {
svrErr <- http.ListenAndServe(":8080", serveSvr)
}()
// start TCP server
go func() {
lnr, err := net.Listen("tcp", ":1111")
if err != nil {
svrErr <- err
return
}
defer lnr.Close()
for {
conn, err := lnr.Accept()
if err != nil {
log.Printf("connection error: %v", err)
continue
}
// code to handle each connection
}
}()
select {
case err := <-svrErr:
log.Print(err)
}
}
I run both servers in separate goroutines and I need a way to gracefully shut them both down if either of them fail. For example; if the HTTP server errors, how would I go back and shutdown the TCP server/perform any cleanup?
Start by keeping a reference to the http server and the tcp listener so that you can later close them.
Create separate error channels so you know which path returned the error, and buffer them so that a send can always complete.
To make sure that whatever cleanup you want to attempt is complete before you exit, you can add a WaitGroup to the server goroutines.
I simple extension of your example might look like:
var wg sync.WaitGroup
// create HTTP server.
serveSvr := http.NewServeMux()
serveSvr.HandleFunc("/", handler())
server := &http.Server{Addr: ":8080", Handler: serveSvr}
// create http server error channel
httpErr := make(chan error, 1)
// start HTTP server.
wg.Add(1)
go func() {
defer wg.Done()
httpErr <- server.ListenAndServe()
// http cleanup
}()
tcpErr := make(chan error, 1)
listener, err := net.Listen("tcp", ":1111")
if err != nil {
tcpErr <- err
} else {
// start TCP server
wg.Add(1)
go func() {
defer wg.Done()
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Temporary() {
// temp error, wait and continue
continue
}
tcpErr <- err
// cleanup TCP
return
}
// code to handle each connection
}
}()
}
select {
case err := <-httpErr:
// handle http error and close tcp listen
if listener != nil {
listener.Close()
}
case err := <-tcpErr:
// handle tcp error and close http server
server.Close()
}
// you may also want to receive the error from the server
// you shutdown to log
// wait for any final cleanup to finish
wg.Wait()

Resources