Golang handlefunc with channel - go

I think this question has been asked before (and probably more than once) but I can't find it...
Im learning Go, and I wanted to extend the classical web server example by sending a channel to the "handler".
I have this standard thing:
func hello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello world!")
}
func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":8000", nil)
}
And now I would like the "hello" function to be able to write stuff on a channel, for someone to consume... The way I have done with "normal" functions is to create a channel:
c := make(chan string)
and pass c in the call to the function. Something like:
dosomething(c)
But... how would I go about doing that if I want "hello" to get access to the channel c?

There are two other ways to do this (other than exporting your channels as in the previous answer).
The first is to use a function to return another handler function. When the function is returned, it will create a closure around the channel.
func makeHello(logger chan string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
logger <- r.Host
io.WriteString(w, "Hello world!")
}
}
The second is to use a struct which holds the channel as a member and use pointer receiver methods to handle the request...
type DataPasser struct {
logs chan string
}
func (p *DataPasser) handleHello(w http.ResponseWriter, r *http.Request) {
p.logs <- r.URL.String()
io.WriteString(w, "Hello world")
}
This is a full working example (just hit /1 and /2 to see the two examples)
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
// METHOD 1
logs := make(chan string)
go logLogs(logs)
handleHello := makeHello(logs)
// METHOD 2
passer := &DataPasser{logs: make(chan string)}
go passer.log()
http.HandleFunc("/1", handleHello)
http.HandleFunc("/2", passer.handleHello)
http.ListenAndServe(":9999", nil)
}
// METHOD 1
func makeHello(logger chan string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
logger <- r.Host
io.WriteString(w, "Hello world!")
}
}
func logLogs(logger chan string) {
for item := range logger {
fmt.Println("1. Item", item)
}
}
// METHOD 2
type DataPasser struct {
logs chan string
}
func (p *DataPasser) handleHello(w http.ResponseWriter, r *http.Request) {
p.logs <- r.URL.String()
io.WriteString(w, "Hello world")
}
func (p *DataPasser) log() {
for item := range p.logs {
fmt.Println("2. Item", item)
}
}

There a few ways to solve this problem, the simplest is to define an exported channel in a package and import said package where ever you want to use the channel.
package mychannel
var Chan = make(chan string)

Related

HTTP Middleware and Google Cloud Functions

What’s the equivalent to middleware handlers in Google Cloud Functions?
In standard approach, normally I do:
router.Handle("/receive", middlewares.ErrorHandler(MyReceiveHandler))
And then, in the middleware:
type ErrorHandler func(http.ResponseWriter, *http.Request) error
func (fn ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := fn(w, r)
if err == nil {
return
}
log.Printf("An error accured: %v", err)
clientError, ok := err.(errors.BaseError)
if !ok {
w.WriteHeader(500)
return
}
w.WriteHeader(clientError.GetStatusCode())
w.Write([]byte(clientError.Error()))
}
In AWS Lambda, I can achieve the same thing using, for example:
func main() {
lambda.Start(
middlewares.Authentication(Handler),
)
}
But I could not find a way to do this in GCP Functions.
How would it work?
Can you help me?
Let's say you start with the following server code in your development environment:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.Handle("/", MiddlewareFinalMsg(" Goodbye!", http.HandlerFunc(HelloWorld)))
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
func MiddlewareFinalMsg(msg string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
fmt.Fprint(w, msg)
})
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "Hello, World!")
}
As far as I can tell, GCF requires its entry point to be an exported identifier of type func(http.ResponseWriter, *http.Request) (not http.HandlerFunc, not http.Handler); therefore, if you have a http.Handler, you'll need to select its ServeHTTP method explicitly to obtain a function of the expected type. However, that identifier can be a package-level function, a method, or a variable.
Here is how you can adapt the code above for GCF:
package p
import (
"fmt"
"net/http"
)
// use F as your GCF's entry point
var F = MiddlewareFinalMsg(" Goodbye!", http.HandlerFunc(HelloWorld)).ServeHTTP
func MiddlewareFinalMsg(msg string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
fmt.Fprint(w, msg)
})
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "Hello, World!")
}

How to access handler value globally

I have this simple http server. How can i access the request data to a global variable and use it in any part of the application.
package main
import (
"io"
"net/http"
)
var data string // Get URL data globally and use it in other part of the application
func hello(w http.ResponseWriter, r *http.Request) {
data := r.URL.Query().Get("somestring")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", hello)
http.ListenAndServe(":8000", mux)
}
You could use net/context with http.Handler. for example you have "X-Request-ID" in header, you could define middlware like this:
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
ctx := newContextWithRequestID(req.Context(), req)
next.ServeHTTP(rw, req.WithContext(ctx))
})
}
type key int
const requestIDKey key = 0
func newContextWithRequestID(ctx context.Context, req *http.Request) context.Context {
reqID := req.Header.Get("X-Request-ID")
if reqID == "" {
reqID = generateRandomID()
}
return context.WithValue(ctx, requestIDKey, reqID)
}
func requestIDFromContext(ctx context.Context) string {
return ctx.Value(requestIDKey).(string)
}
you could get requestIDKey in any handler with Context object.
func handler(rw http.ResponseWriter, req *http.Request) {
reqID := requestIDFromContext(req.Context())
fmt.Fprintf(rw, "Hello request ID %v\n", reqID)
}
this is just an example. insted of requestIDKey you could have any data which you should put in Context and read from it with a key.
for more detail information visit this blog.

Using channel with websocket

If I use a chan anywhere on main or func home, the application runs, but it doesn't really work. No errors thrown, however, it won't work.
If I remove the channels references, it goes back to working.
Either by using a chan in a struct or a global channel the application stops working.
In a GET request, it returns h.Message from func home
by adding any channel in the code, GET request wont return the message.
https://play.golang.org/p/-ZVcLhZRRRG
package main
import (
"fmt"
"net/http"
"github.com/gorilla/websocket"
// _ "github.com/go-sql-driver/mysql"
)
type WMessage struct {
Message string `json:"message"`
ch chan string
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
var Chann chan string
func (h *WMessage) home(w http.ResponseWriter, r *http.Request) {
h.Message = "hey this is the message from home"
fmt.Fprintln(w, h.Message)
fmt.Println(<-h.ch)
}
func main() {
hom := &WMessage{}
hom.ch = make(chan string)
hom.ch <- "message sent"
http.HandleFunc("/", hom.home)
err := http.ListenAndServe(":3000", nil)
if err != nil {
fmt.Println("there was an error -> ", err)
}
}
Unbuffered channels are blocking in nature, i.e. writing to channel (hom.ch <- "message sent") and reading from channel (fmt.Println(<-h.ch)) block the go-routine.
In your case, http server is not running because hom.ch <- "message sent" blocks the execution. That's why GET request is not working.
One simple solution can be to make it buffered channel instead.

How to test reverse proxy with martini in go

I'm writing test code for martini app working as a reverse proxy in go, and want to test it using httptest.ResponseRecorder, but I got an error the following.
[martini] PANIC: interface conversion: *httptest.ResponseRecorder is not http.CloseNotifier: missing method CloseNotify
httptest.ResponseRecorder has no method CloseNotify()
How should I test it?
package main
import (
"github.com/go-martini/martini"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"testing"
)
func TestReverseProxy(t *testing.T) {
// Mock backend
backendResponse := "I am the backend"
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(backendResponse))
}))
defer backend.Close()
backendURL, _ := url.Parse(backend.URL)
// Frontend
m := martini.Classic()
m.Get("/", func(w http.ResponseWriter, r *http.Request) {
proxy := httputil.NewSingleHostReverseProxy(backendURL)
proxy.ServeHTTP(w, r)
})
// Testing
req, _ := http.NewRequest("GET", "/", nil)
res := httptest.NewRecorder()
m.ServeHTTP(res, req)
assert.Equal(t, 200, res.Code, "should be equal")
}
First, please note that the martini framework is no longer maintained as said in their README.
Then, about your issue, it's because Martini does something that looks pretty bad to me: it takes an http.ResponseWriter and assumes it is also an http.CloseNotifier, while there is absolutely no guarantee of this. They should take a custom interface wrapping both of them, something like that:
type ResponseWriterCloseNotifier interface {
http.ResponseWriter
http.CloseNotifier
}
You can see in their source code that they had the same issue for their own tests, and used some workaround: https://github.com/go-martini/martini/commit/063dfcd8b0f64f4e2c97f0bc27fa422969baa23b#L13
Here is some working code made with it:
package main
import (
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"testing"
"github.com/go-martini/martini"
"github.com/stretchr/testify/assert"
)
type closeNotifyingRecorder struct {
*httptest.ResponseRecorder
closed chan bool
}
func newCloseNotifyingRecorder() *closeNotifyingRecorder {
return &closeNotifyingRecorder{
httptest.NewRecorder(),
make(chan bool, 1),
}
}
func (c *closeNotifyingRecorder) close() {
c.closed <- true
}
func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
return c.closed
}
func TestReverseProxy(t *testing.T) {
// Mock backend
backendResponse := "I am the backend"
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(backendResponse))
}))
defer backend.Close()
backendURL, _ := url.Parse(backend.URL)
// Frontend
m := martini.Classic()
m.Get("/", func(w http.ResponseWriter, r *http.Request) {
proxy := httputil.NewSingleHostReverseProxy(backendURL)
proxy.ServeHTTP(w, r)
})
// Testing
req, _ := http.NewRequest("GET", "/", nil)
res := newCloseNotifyingRecorder()
m.ServeHTTP(res, req)
assert.Equal(t, 200, res.Code, "should be equal")
}

How can I use HttpRouter with MuxChain?

I'd like to use httprouter with muxchain while keeping route parameters like /:user/.
Take the following example:
func log(res http.ResponseWriter, req *http.Request) {
fmt.Println("some logger")
}
func index(res http.ResponseWriter, req *http.Request) {
fmt.Fprintf(res, "Hi there, I love %s!", req.URL.Path[1:])
}
func main() {
logHandler := http.HandlerFunc(log)
indexHandler := http.HandlerFunc(index)
chain := muxchain.ChainHandlers(logHandler, indexHandler)
router := httprouter.New()
router.Handler("GET", "/:user", chain)
http.ListenAndServe(":8080", router)
}
When I visit http://localhost:8080/john I obviously don't have access to ps httprouter.Params
That's because httprouter needs to see type httprouter.Handle but the function is called with type http.Handler.
Is there any way to use both packages together? The HttpRouter GitHub repo says
The only disadvantage is, that no parameter values can be retrieved when a http.Handler or http.HandlerFunc is used, since there is no efficient way to pass the values with the existing function parameters.
If you strongly want to use that packages, you can try to do something like that:
package main
import (
"fmt"
"github.com/gorilla/context"
"github.com/julienschmidt/httprouter"
"github.com/stephens2424/muxchain"
"net/http"
)
func log(res http.ResponseWriter, req *http.Request) {
fmt.Printf("some logger")
}
func index(res http.ResponseWriter, req *http.Request) {
p := context.Get(req, "params").(httprouter.Params)
fmt.Fprintf(res, "Hi there, I love %s!", p.ByName("user"))
}
func MyContextHandler(h http.Handler) httprouter.Handle {
return func(res http.ResponseWriter, req *http.Request, p httprouter.Params) {
context.Set(req, "params", p)
h.ServeHTTP(res, req)
}
}
func main() {
logHandler := http.HandlerFunc(log)
indexHandler := http.HandlerFunc(index)
chain := muxchain.ChainHandlers(logHandler, indexHandler)
router := httprouter.New()
router.GET("/:user", MyContextHandler(chain))
http.ListenAndServe(":8080", router)
}
You would have to patch muxchain to accept httprouter.Handle, but it's rather simple to create your own chain handler, for example:
func chain(funcs ...interface{}) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
for _, h := range funcs {
switch h := h.(type) {
case httprouter.Handle:
h(w, r, p)
case http.Handler:
h.ServeHTTP(w, r)
case func(http.ResponseWriter, *http.Request):
h(w, r)
default:
panic("wth")
}
}
}
}
playground

Resources