I want to wire RecoverWrap to all handlers of martini routes to make any panic be finished by code inside RecoverWrap.
I tried to do it like m.Use(RecoverWrap) but do not know how to do it exactly, it fails on compile.
package main
import (
"errors"
"github.com/go-martini/martini"
"net/http"
)
func main() {
m := martini.Classic()
//m.Use(RecoverWrap)
m.Get("/", func() {
panic("some panic")
})
m.Run()
}
func RecoverWrap(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var err error
defer func() {
r := recover()
if r != nil {
switch t := r.(type) {
case string:
err = errors.New(t)
case error:
err = t
default:
err = errors.New("Unknown error")
}
http.Error(w, "Something goes wrong", http.StatusInternalServerError)
}
}()
h.ServeHTTP(w, req)
})
}
Middleware handlers in Martini do not get to "wrap" other handler calls, so http.Handler is not found by the injector.
What you can do is use context.Next():
package main
import (
"errors"
"github.com/go-martini/martini"
"net/http"
)
func main() {
m := martini.Classic()
m.Use(RecoverWrap)
m.Get("/", func() {
panic("some panic")
})
m.Run()
}
func RecoverWrap(c martini.Context, w http.ResponseWriter) {
var err error
defer func(w http.ResponseWriter) {
r := recover()
if r != nil {
switch t := r.(type) {
case string:
err = errors.New(t)
case error:
err = t
default:
err = errors.New("Unknown error")
}
http.Error(w, "Something goes wrong", http.StatusInternalServerError)
}
}(w)
c.Next()
}
You will have to make sure that your error handler is the first middleware registered, or those handlers running before will not be caught.
Actually, the same method is implemented in martini.Recovery:
https://github.com/go-martini/martini/blob/6241001738f6e1b1ea7c4a4089195e1b0681609a/recovery.go#L115
Related
Hello Everyone, I'm new to Go.
I'm creating a reverse proxy server using Go.
My Server has websocket. I finally get it connected.
Now I want to change websocket message body.
Sorry If my code is weird to you. Forgive me, I'm new to Go 😢
I'm wraping resp.body to NewReadWriteBody() in which contains wrapper for Read, Write and Closer. And I'm modifying message body inside it.
Here is how I'm doing with it:
package rever
// https://blog.joshsoftware.com/2021/05/25/simple-and-powerful-reverseproxy-in-go/
// https://github.com/golang/go/blob/master/src/net/http/httputil/reverseproxy.go
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
)
type ReadWriteBody struct {
originBody io.ReadWriteCloser
}
func NewReadWriteBody(body io.ReadCloser) *ReadWriteBody {
b := &ReadWriteBody{}
rw, ok := body.(io.ReadWriteCloser)
if !ok {
log.Println("29: error while casting body to ReadWriteCloser")
}
b.originBody = rw
return b
}
func (b *ReadWriteBody) Read(p []byte) (n int, err error) {
buf := make([]byte, len(p))
n, err = b.originBody.Read(buf)
if err != nil {
log.Println("43: ", err.Error())
return n, err
}
buf = bytes.ReplaceAll(buf, []byte("mm.remote"), []byte("mm.local"))
copy(p[:], buf)
return len(p), nil
}
func (b *ReadWriteBody) Write(p []byte) (n int, err error) {
buf := make([]byte, len(p))
n, err = b.originBody.Write(buf)
if err != nil {
log.Println(err.Error())
return n, err
}
buf = bytes.ReplaceAll(buf, []byte("mm.local"), []byte("mm.remote"))
copy(p[:], buf)
return len(p), nil
}
func (b *ReadWriteBody) Close() error {
return b.originBody.Close()
}
type transport struct {
http.RoundTripper
}
func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
resp, err = t.RoundTripper.RoundTrip(req)
if err != nil {
log.Println("99: ", err.Error())
return nil, err
}
if resp.StatusCode == http.StatusSwitchingProtocols {
resp.Body = NewReadWriteBody(resp.Body)
return resp, nil
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("114: ", err.Error())
return nil, err
}
err = resp.Body.Close()
if err != nil {
log.Println("119", err.Error())
return nil, err
}
b = bytes.ReplaceAll(b, []byte("mm.remote"), []byte("mm.local"))
body := ioutil.NopCloser(bytes.NewReader(b))
resp.Body = body
resp.ContentLength = int64(len(b))
resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
return resp, nil
}
var _ http.RoundTripper = &transport{}
// NewProxy takes target host and creates a reverse proxy
func NewProxy(targetHost string) (*httputil.ReverseProxy, error) {
url, err := url.Parse(targetHost)
if err != nil {
log.Println("141: ", err.Error())
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(url)
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
modifyRequest(req)
}
proxy.ErrorHandler = errorHandler()
dt := http.DefaultTransport.(*http.Transport).Clone()
dt.TLSClientConfig = &tls.Config{}
dt.ForceAttemptHTTP2 = false
proxy.Transport = &transport{dt}
return proxy, nil
}
func modifyRequest(req *http.Request) {
req.Host = "mm.remote"
req.Header.Set("Accept-Encoding", "identity")
}
func errorHandler() func(http.ResponseWriter, *http.Request, error) {
return func(w http.ResponseWriter, req *http.Request, err error) {
// fmt.Printf("Got error while modifying response: %v \n", err)
}
}
// ProxyRequestHandler handles the http request using proxy
func ProxyRequestHandler(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r)
}
}
func Main() {
// initialize a reverse proxy and pass the actual backend server url here
proxy, err := NewProxy("https://mm.remote")
if err != nil {
log.Println(err.Error())
panic(err)
}
// handle all requests to your server using the proxy
http.HandleFunc("/", ProxyRequestHandler(proxy))
fmt.Println("Server started")
log.Fatal(http.ListenAndServe(":8008", nil))
}
I am on go version go1.10.4 linux/amd64
I am registering middleware, but they don't seem to be hit.
package main
import (
"encoding/json"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/context"
"github.com/mitchellh/mapstructure"
"huru/migrations"
"huru/models"
"huru/models/person"
"huru/routes"
"net/http"
"os"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
log "github.com/sirupsen/logrus"
)
func loggingMiddleware(next http.Handler) http.Handler {
log.Println("logging middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println("Here is the request URI:",r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
type Exception struct {
Message string `json:"message"`
}
func authMiddleware(next http.Handler) http.Handler {
log.Println("auth middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
fmt.Println("the params are:", params);
token, _ := jwt.Parse(params["token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("there was an error")
}
return []byte("secret"), nil
})
claims, ok := token.Claims.(jwt.MapClaims)
if ! (ok && token.Valid) {
json.NewEncoder(w).Encode(Exception{Message: "Invalid authorization token"})
return;
}
var user person.Model
mapstructure.Decode(claims, &user)
context.Set(r, "logged_in_user", user)
next.ServeHTTP(w, r)
})
}
func errorMiddleware(next http.Handler) http.Handler {
log.Println("error handling middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Error("Caught error in defer/recover middleware: ", err)
originalError := err.(struct{ OriginalError error }).OriginalError
if originalError != nil {
log.Error("Original error in defer/recover middleware: ", originalError)
}
statusCode := err.(struct{ StatusCode int }).StatusCode
if statusCode != 0 {
w.WriteHeader(statusCode)
} else {
w.WriteHeader(http.StatusInternalServerError)
}
message := err.(struct{ Message string }).Message
if message == "" {
message = "Unknown error message."
}
json.NewEncoder(w).Encode(struct {
ID string
}{
message,
})
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
routerParent := mux.NewRouter()
routerParent.Use(loggingMiddleware)
routerParent.Use(errorMiddleware)
routerParent.Use(authMiddleware)
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
// register and login
{
handler := routes.LoginHandler{}
subRouter := router.PathPrefix("/").Subrouter()
handler.Mount(subRouter, struct{}{});
}
{
handler := routes.RegisterHandler{}
subRouter := router.PathPrefix("/").Subrouter()
handler.Mount(subRouter, struct{}{})
}
{
// people
handler := routes.PersonHandler{}
subRouter := router.PathPrefix("/").Subrouter()
subRouter.Use(authMiddleware)
handler.Mount(subRouter, routes.PersonInjection{People: models.PersonInit()})
}
// ...
}
none of these get logged:
log.Println("error handling middleware registered");
log.Println("auth middleware registered");
log.Println("logging middleware registered");
and at runtime none of middleware routes seem to get hit, nothing is logged there. Anyone know why that may be?
Note that I don't expect to need all these:
routerParent := mux.NewRouter()
routerParent.Use(loggingMiddleware)
routerParent.Use(errorMiddleware)
routerParent.Use(authMiddleware)
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
in reality I probably just want:
routerParent := mux.NewRouter()
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
but it's just there to prove that something is off. At the end of the main func, I have this to start the server:
host := os.Getenv("huru_api_host")
port := os.Getenv("huru_api_port")
if host == "" {
host = "localhost"
}
if port == "" {
port = "80"
}
log.Info(fmt.Sprintf("Huru API server listening on port %s", port))
path := fmt.Sprintf("%s:%s", host, port)
log.Fatal(http.ListenAndServe(path, routerParent))
I only want to support HTTP/2 for a new project, the client is not a browser so it's not a problem if we don't support HTTP/1.x at all.
from what I see in golang.org/x/net/http2. I can use tls.Listen and pass the net.Conn to http2.Server.ServeConn.
But I'm bit confused about how to use http2.Transport here, can anyone give me an example?
Thanks
UPDATE:
This is the server part, pretty simple, it's an echo server
package main
import (
"fmt"
"io"
"net"
"net/http"
"golang.org/x/net/http2"
)
func main() {
l, err := net.Listen("tcp4", ":1234")
panicIfNotNil(err)
s := &http2.Server{}
sopt := &http2.ServeConnOpts{
BaseConfig: &http.Server{},
Handler: http.HandlerFunc(handler),
}
for {
c, err := l.Accept()
panicIfNotNil(err)
go serve(s, sopt, c)
}
}
func serve(s *http2.Server, sopt *http2.ServeConnOpts, c net.Conn) {
defer c.Close()
s.ServeConn(c, sopt)
}
func handler(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor != 2 {
w.WriteHeader(500)
fmt.Fprintln(w, "Not HTTP/2")
return
}
f, ok := w.(http.Flusher)
if !ok {
w.WriteHeader(500)
fmt.Fprintln(w, "Not Flusher")
return
}
w.Header().Set("Content-Type", "application/octet-stream")
fmt.Fprintln(w, "Hello World, Echo Server")
buf := [1024]byte{}
for {
n, err := r.Body.Read(buf[:])
if err == io.EOF {
break
}
panicIfNotNil(err)
_, err = w.Write(buf[:n])
f.Flush()
panicIfNotNil(err)
}
}
func panicIfNotNil(err error) {
if err != nil {
panic(err)
}
}
tested with curl --http2-prior-knowledge http://127.0.0.1:1234 -d a=b -d c=d -d e=f
for the client part, I'm still trying, I will update this post again when I got something.
UPDATE:
for the sake of simplicity, I don't use TLS here
UPDATE:
This is the client part
package main
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"time"
"golang.org/x/net/http2"
)
func main() {
t := &http2.Transport{
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
AllowHTTP: true,
}
c := &http.Client{
Transport: t,
}
pr, pw := io.Pipe()
req := &http.Request{
Method: "POST",
URL: mustUrl("http://127.0.0.1:1234/"),
Body: pr,
}
resp, err := c.Do(req)
panicIfNotNil(err)
defer resp.Body.Close()
if resp.StatusCode != 200 {
panic(fmt.Errorf("Server return non 200, %d", resp.StatusCode))
}
wchan := make(chan struct{})
go func() {
buf := [1024]byte{}
for {
n, err := resp.Body.Read(buf[:])
if err == io.EOF {
break
}
panicIfNotNil(err)
fmt.Printf("GOT DATA %s\n", string(buf[:n]))
}
close(wchan)
}()
time.Sleep(1 * time.Second)
pw.Write([]byte("hai AAA"))
time.Sleep(1 * time.Second)
pw.Write([]byte("hai BBB"))
time.Sleep(1 * time.Second)
pw.Write([]byte("hai CCC"))
time.Sleep(1 * time.Second)
pw.Write([]byte("hai CCC"))
time.Sleep(1 * time.Second)
pw.Close()
<-wchan
}
func mustUrl(s string) *url.URL {
r, err := url.Parse(s)
panicIfNotNil(err)
return r
}
func panicIfNotNil(err error) {
if err != nil {
panic(err)
}
}
but somehow it doesn't work
You can see network traffic in https://imgur.com/EJV0uGI
After looking into Wireshark more closely I found the problem, it happens because the server didn't send any header frame, so the client cannot continue with more data. Just printing into http.ResponseWriter doesn't ensure its written into the network, it gets buffered instead, so we need to explicitly flush it.
This fixes the problem:
--- main.go 2018-07-25 22:31:44.092823590 +0700
+++ main2.go 2018-07-25 22:32:50.586179879 +0700
## -43,6 +43,9 ##
return
}
w.Header().Set("Content-Type", "application/octet-stream")
+ w.WriteHeader(200)
+ f.Flush()
+
fmt.Fprintln(w, "Hello World, Echo Server")
buf := [1024]byte{}
I want to send email to myself with error occured on page http://localhost:3000/panic with containing error url - /panic in our case. But I can not figure out how to get url from c martini.Context inside RecoverWrap method.
package main
import (
"errors"
"github.com/go-martini/martini"
"net/http"
)
func main() {
m := martini.Classic()
m.Use(RecoverWrap)
m.Get("/panic", func() {
panic("some panic")
})
m.Get("/", func(req *http.Request, res http.ResponseWriter) {
res.Write([]byte("mainPage"))
})
m.Run()
}
func RecoverWrap(c martini.Context, w http.ResponseWriter) {
var err error
defer func(w http.ResponseWriter) {
r := recover()
if r != nil {
switch t := r.(type) {
case string:
err = errors.New(t)
case error:
err = t
default:
err = errors.New("Unknown error")
}
// how to get request url here
// I want to send email with error url
http.Error(w, "Something goes wrong", http.StatusInternalServerError)
}
}(w)
c.Next()
}
The answer is in adding req *http.Request parameter to func RecoverWrap(c martini.Context, req *http.Request, w http.ResponseWriter)
Full code:
package main
import (
"errors"
"fmt"
"github.com/go-martini/martini"
"net/http"
)
func main() {
m := martini.Classic()
m.Use(RecoverWrap)
m.Get("/panic", func() {
panic("some panic")
})
m.Get("/", func(req *http.Request, res http.ResponseWriter) {
res.Write([]byte("mainPage"))
})
m.Run()
}
func RecoverWrap(c martini.Context, req *http.Request, w http.ResponseWriter) {
var err error
defer func(w http.ResponseWriter) {
r := recover()
if r != nil {
switch t := r.(type) {
case string:
err = errors.New(t)
case error:
err = t
default:
err = errors.New("Unknown error")
}
fmt.Println("req.URL.Path")
fmt.Println(req.URL.Path)
http.Error(w, "Something goes wrong", http.StatusInternalServerError)
}
}(w)
c.Next()
}
I want to create global err handler to send it by email.
package main
import (
"github.com/gorilla/mux"
"log"
"net/http"
)
func main() {
rtr := mux.NewRouter()
rtr.HandleFunc("/", withPanic).Methods("GET")
http.Handle("/", rtr)
log.Println("Listening...")
http.ListenAndServe(":3001", http.DefaultServeMux)
}
func withPanic(w http.ResponseWriter, r *http.Request) {
panic("somewhere here will be panic, but I don't know where exactly")
}
How to make it global. It would be easy if I know where error will occur
if err != nil {
sendMeMail(err)
}
But what to do in cases when I don't know exactly where an error will occur? So I should add a global recoverish handler. But how to do it exactly I don't know.
Update
I added defer recover to beginning of main but it never executes on requesting http://localhost:3001. So panic is not emailed.
package main
import (
"errors"
"fmt"
"github.com/gorilla/mux"
"log"
"net/http"
)
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
// find out exactly what the error was and set err
var err error
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("Unknown panic")
}
if err != nil {
// sendMeMail(err)
fmt.Println("sendMeMail")
}
}
}()
rtr := mux.NewRouter()
rtr.HandleFunc("/", withPanic).Methods("GET")
http.Handle("/", rtr)
log.Println("Listening...")
http.ListenAndServe(":3001", http.DefaultServeMux)
}
func withPanic(w http.ResponseWriter, r *http.Request) {
panic("somewhere here will be panic, but I don't know where exactly")
}
You can wrap your handlers in a recovery middleware
package main
import (
"errors"
"github.com/gorilla/mux"
"log"
"net/http"
)
func main() {
m := mux.NewRouter()
m.Handle("/", RecoverWrap(http.HandlerFunc(handler))).Methods("GET")
http.Handle("/", m)
log.Println("Listening...")
http.ListenAndServe(":3001", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
panic(errors.New("panicing from error"))
}
func RecoverWrap(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
r := recover()
if r != nil {
var err error
switch t := r.(type) {
case string:
err = errors.New(t)
case error:
err = t
default:
err = errors.New("Unknown error")
}
sendMeMail(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}()
h.ServeHTTP(w, r)
})
}
func sendMeMail(err error) {
// send mail
}
You can take a a look at codahale recovery handler or negroni middleware for more details.
I believe that is what the gorilla recovery handler is for