Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
github.com/augustoroman/sandwich
Keep pilin' it on!
Sandwich is a middleware & routing framework that lets you write your handlers and middleware the way you want to and it takes care of tracking & validating dependencies.
Here's a very simple example of using sandwich with the standard HTTP stack:
package main
import (
"fmt"
"log"
"net/http"
"github.com/augustoroman/sandwich"
)
func main() {
// Create a default sandwich middlware stack that includes logging and
// a simple error handler.
mux := sandwich.TheUsual()
mux.Get("/", func(w http.ResponseWriter) {
fmt.Fprintf(w, "Hello world!")
})
if err := http.ListenAndServe(":6060", mux); err != nil {
log.Fatal(err)
}
}
See the examples directory for:
Sandwich automatically calls your middleware with the necessary arguments to run them based on the types they require. These types can be provided by previous middleware or directly during the initial setup.
For example, you can use this to provide your database to all handlers:
func main() {
db_conn := ConnectToDatabase(...)
mux := sandwich.TheUsual()
mux.Set(db_conn)
mux.Get("/", Home)
}
func Home(w http.ResponseWriter, r *http.Request, db_conn *Database) {
// process the request here, using the provided db_conn
}
Set(...) and SetAs(...) are excellent alternatives to using global values, plus they keep your functions easy to test!
In many cases you want to initialize a value based on the request, for example extracting the user login:
func main() {
mux := sandwich.TheUsual()
mux.Get("/", ParseUserCookie, SayHi)
}
// You can write & test exactly this signature:
func ParseUserCookie(r *http.Request) (User, error) { ... }
// Then write your handler assuming User is available:
func SayHi(w http.ResponseWriter, u User) {
fmt.Fprintf(w, "Hello %s", u.Name)
}
This starts to show off the real power of sandwich. For each request, the following occurs:
ParseUserCookie
is called. If it returns a non-nil error,
sandwich's HandleError
is called and the request is aborted. If the error
is nil, processing continues.SayHi
is called with User
returned from ParseUserCookie
.This allows you to write small, independently testable functions and let sandwich chain them together for you. Sandwich works hard to ensure that you don't get annoying run-time errors: it's structured such that it must always be possible to call your functions when the middleware is initialized rather than when the http handler is being executed, so you don't get surprised while your server is running.
When a handler returns an error, sandwich aborts the middleware chain and looks for the most recently registered error handler and calls that. Error handlers may accept any types that have been provided so far in the middleware stack as well as the error type. They must not have any return values.
Here's an example of rendering errors with a custom error page:
type ErrorPageTemplate *template.Template
func main() {
tpl := template.Must(template.ParseFiles("path/to/my/error_page.tpl"))
mux := sandwich.TheUsual()
mux.Set(ErrorPageTemplate(tpl))
mux.OnErr(MyErrorHandler)
...
}
func MyErrorHandler(w http.ResponseWriter, t ErrorPageTemplate, l *sandwich.LogEntry, err error) {
if err == sandwich.Done { // sandwich.Done can be returned to abort middleware.
return // It indicates there was no actual error, so just return.
}
// Unwrap to a sandwich.Error that has Code, ClientMsg, and internal LogMsg.
e := sandwich.ToError(err)
// If there's an internal log message, add it to the request log.
e.LogIfMsg(l)
// Respond with my custom html error page, including the client-facing msg.
w.WriteHeader(e.Code)
t.Execute(w, map[string]string{Msg: e.ClientMsg})
}
Error handlers allow you consolidate the error handling of your web app. You can customize the error page, assign user-facing error codes, detect and fire alerts for certain errors, and control which errors get logged -- all in one place.
By default, sandwich never sends internal error details to the client and insteads logs the details.
Sandwich also allows registering handlers to run during AND after the middleware (and error handling) stack has completed. This is especially useful for handles such as logging or gzip wrappers. Once the before handle is run, the 'after' handlers are queued to run and will be run regardless of whether an error aborts any subsequent middleware handlers.
Typically this is done with the first function creating and initializing some state to pass to the deferred handler. For example, the logging handlers are:
// NewLogEntry creates a *LogEntry and initializes it with basic request
// information.
func NewLogEntry(r *http.Request) *LogEntry {
return &LogEntry{Start: time.Now(), ...}
}
// Commit fills in the remaining *LogEntry fields and writes the entry out.
func (entry *LogEntry) Commit(w *ResponseWriter) {
entry.Elapsed = time.Since(entry.Start)
...
WriteLog(*entry)
}
and are added to the chain using:
var LogRequests = Wrap{NewLogEntry, (*LogEntry).Commit}
In this case, NewLogEntry
returns a *LogEntry
that is then provided to
downstream handlers, including the deferred Commit handler -- in this case a
method expression that takes
the *LogEntry
as its value receiver.
Unfortunately, set interface values is a little tricky. Since interfaces in Go are only used for static typing, the encapsulation isn't passed to functions that accept interface{}, like Set().
This means that if you have an interface and a concrete implementation, such as:
type UserDatabase interface{
GetUserProfile(u User) (Profile, error)
}
type userDbImpl struct { ... }
func (u *userDbImpl) GetUserProfile(u User) (Profile, error) { ... }
You cannot provide this to handlers directly via the Set() call.
udb := &userDbImpl{...}
// DOESN'T WORK: this will provide *userDbImpl, not UserDatabase
mux.Set(udb)
mux.Set((UserDatabase)(udb)) // DOESN'T WORK EITHER
udb_iface := UserDatabase(udb)
mux.Set(&udb_iface) // STILL DOESN'T WORK!
Instead, you have to either use SetAs() or a dedicated middleware function that returns the interface:
udb := &userDbImpl{...}
// either use SetAs() with a pointer to the interface
mux.SetAs(udb, (*UserDatabase)(nil))
// or add a handler that returns the interface
mux.Use(func() UserDatabase { return udb })
It's a bit silly, but there you are.
Sandwich uses reflection-based dependency-injection to call the middleware functions with the parameters they need.
Q: OMG reflection and dependency-injection, isn't that terrible and slow and non-idiomatic go?!
Whoa, nelly. Let's deal with those one at time, m'kay?
Q: Isn't reflection slow?
Not compared to everything else a webserver needs to do.
Yes, sandwich's reflection-based dependency-injection code is slower than middleware code that directly calls functions, however the vast majority of server code (especially during development) is not impacted by time spent calling a few functions, but rather by HTTP network I/O, request parsing, database I/O, response marshalling, etc.
Q: Ok, but aren't both reflection and dependency-injection non-idiomatic Go?
Sorta. The use of reflection in and of itself isn't non-idiomatic, but the use of magical dependency injection is: Go eschews magic.
However, one of the major goals of this library is to allow the HTTP handler code (and all middleware) to be really clean, idiomatic go functions that are testable by themselves. The idea is that the magic is small, contained, doesn't leak, and provides substantial benefit.
Q: But wait, don't you get annoying run-time "dependency-not-found" errors with dependency-injection?
While it's true that you can't get the same compile-time checking that you do with direct-call-based middleware, sandwich works really hard to ensure that you don't get surprises while running your server.
At the time each middleware function is added to the stack, the library ensures that it's dependencies have been explicitly provided. One of the features of sandwich is that you can't arbitrary inject values -- they need to have an explicit provisioning source.
Q: Doesn't the http.Request.Context in go 1.7 solve the middleware dependency problem?
Have a request-scoped context allows you to pass values between middleware handlers, it's true. However, there's no guarantee that the values are available, so you get the same run-time bugs that you might get with a naive dependency-injection framework. In addition, you have to do type-assertions to get your values, so there's another possible source of bugs. One of the goals of sandwich is to avoid these two types of bugs.
Q: Why do I have to use two functions (before & after) to wrap a request. Why can't I just have one with a next() function?
Many middleware frameworks provide the capability to wrap a request via a next() function. Sometimes it's part of a context object (martini's Context.Next(), gin's Context.Next()) and sometimes it's directly provided (negroni's third handler arg).
While implementing sandwich, I initially included a next()
function until I
realized it was impossible to validate the dependencies with such a function.
Sandwich guarantees that dependencies can be supplied, and therefore next()
had to go.
Instead, I took a tip from go and instead implemented defer. The wrap interface simply makes it obvious that there's a before and after. This allows me to keep my dependency guarantee.
Q: I don't know, it's still scary and terrible!
Don't get scared off. Take a look at the library, try it out, and I hope you enjoy it. If you don't, there are lots of great alternatives.
FAQs
Unknown package
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.