I would like to put all my routes in a routes.go file. Currently I have a class here:
cmd-> src -> main.go
Where I have this line of code:
func startServer(port string, services Services, stop chan os.Signal, psFact *ps.Factory, logger log.Logger) *http.Server {
router := mux.NewRouter()
apiHandler.RegisterRoutes(router)
router.HandleFunc
srv := &http.Server{Addr: fmt.Sprintf(":%v", port), Handler: router}
go func() {
if err := srv.ListenAndServe(); err != nil {
if strings.HasPrefix(err.Error(), "listen tcp :5002: bind") {
stop <- syscall.SIGTERM
}
log.Log.Error("error shutting down server", zap.Error(err))
}
}()
return srv
}
I would like to throw all my router.HandleFuncs in a separate file called routes.go in the cmd ->src -> routes.go, but am having trouble figuring out how I can do that. Currently, what I have is this:
package main
import "net/http"
func (services Services) routes() {
}
But how can I call these routes.go from my main.go?
I am not sure if I get it... but try something like the following:
$ tree
.
└── cmd
└── src
├── main.go
└── routes.go
2 directories, 2 files
// $ cat cmd/src/main.go
package main
func main() {
println("hello world")
muxRouter := "SuperRouter"
myService := &MyService{}
myService.routes(muxRouter)
}
// $ cat cmd/src/routes.go
package main
import "fmt"
// MyService ...
type MyService struct {
// deps
}
// routes ...
func (ms *MyService) routes(router string) { //
// register here your endpoints & handlers
fmt.Printf("routes - router: %s & GetServices: %s \n", router, ms.GetServices())
}
// GetServices ...
func (ms *MyService) GetServices() string {
return "baz"
}
$ go run cmd/src/*.go
hello world
routes - router: SuperRouter & GetServices: baz
Best,
The actual problem here is that your service knows best what it can provide and your router does not. Since your Services methods are handler functions anyway, you can delegate the setup of the router to the Services. In the example below this is done in BaseRoutes.
Then, all you have to do is to get a router and let the instance of your Service modify the behavior of said router.
This does not prevent you from setting up addition routes, say for providing an HTMl GUI, as shown by the usage of HelloRoute as a variadic arg in NewRouter.
At the end of the day, you hand your Services instance and the additional routes you might want (this is optional!) over to the NewRouter wrapper and can use the result as usual.
NOTE: I did not do any error handling in the Services, as this highly depends on what you actually wanna do. Please put proper though into error handling anyway.
// Services are a dummy for the actual implementation.
// However, using an interface might well make sense if you have multiple Services.
type Services interface {
GetServices(w http.ResponseWriter, r *http.Request)
GetAssetStatus(w http.ResponseWriter, r *http.Request)
UpdateEncoderConfigForLid(w http.ResponseWriter, r *http.Request)
BaseRoutes(router *mux.Router) error
}
// MyServices is dummy implementation of Services.
type MyServices struct {
}
// GetServices implements the Services interface for MyServices
func (s MyServices) GetServices(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("GetServices"))
}
// GetAssetStatus implements the Services interface for MyServices
func (s MyServices) GetAssetStatus(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("GetAssetStatus"))
}
// UpdateEncoderConfigForLid implements the Services interface for MyServices
func (s MyServices) UpdateEncoderConfigForLid(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("UpdateEncoderConfigForLid"))
}
// BaseRoutes will set up the routes
func (s *MyServices) BaseRoutes(router *mux.Router) error {
router.HandleFunc("/api/services", s.GetServices).Methods(http.MethodGet)
router.HandleFunc("/api/services/status", s.GetAssetStatus).Methods(http.MethodGet)
router.HandleFunc("/api/services/{lid}/encoders", s.UpdateEncoderConfigForLid).Methods(http.MethodPut)
return nil
}
// HelloRoute is an example for an additional route you might want to set up
func HelloRoute(router *mux.Router) error {
router.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
return nil
}
type stdRoute func(router *mux.Router) error
// NewRouter wraps the setup of your service routes and additional routes
// We use the Services interface here to make it more generic.
func NewRouter(s Services, additionalRoutes ...stdRoute) *mux.Router {
r := mux.NewRouter()
var err error
if err = s.BaseRoutes(r); err != nil {
log.Printf("setting up service routes: %s => HANDLE THIS!", err)
}
for _, route := range additionalRoutes {
if err = route(r); err != nil {
log.Printf("setting up additional routes: %s => HANDLE THIS!", err)
}
}
return r
}
// Of course, if you have additional services, you can use variadic args again.
func web(s Services) {
router := NewRouter(s, HelloRoute)
log.Fatal(http.ListenAndServe(":8080", router))
}
Related
I've structured my application in two packages so far - main and app
In my main() I start my server:
func main() {
router := app.CreateRouter(app.Routes())
log.Fatal(http.ListenAndServe(":8080", router))
}
In app, I have a Config struct which has a method connectToDB:
type Config struct {
DB *sql.DB
}
func (c *Config) connectToDB() {
connectionString := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", os.Getenv("DB_USERNAME"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"))
var err error
c.DB, err = sql.Open("postgres", connectionString)
if err != nil {
log.Fatal(err)
}
}
func init() {
c := Config{}
c.connectToDB()
}
However, I have various handlers and if I want to make use of Config.DB, how can I do that?
For example, in app.UserIndex, how can I get to Config.DB?
func UserIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "test!\n")
}
Now, one thing I can do is set a global variable, such as
var c Config
Then, I can access c.DB anywhere in package app. However, this feels bad..
You can make an App variable to keep there config and some other useful settings for it eg Http time-out
var (
App struct {
DB *sql.DB
Timeout time.Duration
...
}
)
Then make methods on this structure. This way config will be incapsulated in application instance.
If you want to avoid globals, make your handlers structs instead of pure funcs:
type UserIndex struct {
cfg Config
}
func (u UserIndex) ServeHTTP(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "test!\n")
}
When setting up your routes, use UserIndex{c}.ServeHTTP instead of UserIndex.
The core library http package differentiates between a Handler type and a HandlerFunc for this reason. You appear to be using github.com/julienschmidt/httprouter, which doesn't make this distinction and doesn't provide an interface that matches its httprouter.Handle type, but you can still use a method on a struct to satisfy that type.
I'm reasonably new to golang and am trying to do work out the best way to do this idiomatically.
I have an array of routes I am statically defining and passing to gorilla/mux. I am wrapping each handler function with something to time the request and handle panics (mainly so I could understand how the wrapping worked).
I want them each to be able to have access to a 'context' - a struct that's going to be one-per-http-server, which might have things like database handles, config etc. What I don't want to do is use a static global variable.
The way I'm currently doing it I can give the wrappers access to the context structure, but I can't see how to get this into the actual handler, as it wants that to be an http.HandlerFunc. I thought what I could do is convert http.HandlerFunc into a type of my own that was a receiver for Context (and do similarly for the wrappers, but (after much playing about) I couldn't then get Handler() to accept this.
I can't help but think I'm missing something obvious here. Code below.
package main
import (
"fmt"
"github.com/gorilla/mux"
"html"
"log"
"net/http"
"time"
)
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
type Context struct {
route *Route
// imagine other stuff here, like database handles, config etc.
}
type Routes []Route
var routes = Routes{
Route{
"Index",
"GET",
"/",
index,
},
// imagine lots more routes here
}
func wrapLogger(inner http.Handler, context *Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
log.Printf(
"%s\t%s\t%s\t%s",
r.Method,
r.RequestURI,
context.route.Name,
time.Since(start),
)
})
}
func wrapPanic(inner http.Handler, context *Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic caught: %+v", err)
http.Error(w, http.StatusText(500), 500)
}
}()
inner.ServeHTTP(w, r)
})
}
func newRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
// the context object is created here
context := Context {
&route,
// imagine more stuff here
}
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(wrapLogger(wrapPanic(route.HandlerFunc, &context), &context))
}
return router
}
func index(w http.ResponseWriter, r *http.Request) {
// I want this function to be able to have access to 'context'
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}
func main() {
fmt.Print("Starting\n");
router := newRouter()
log.Fatal(http.ListenAndServe("127.0.0.1:8080", router))
}
Here's a way to do it, but it seems pretty horrible. I can't help but think there must be some better way to do it - perhaps to subclass (?) http.Handler.
package main
import (
"fmt"
"github.com/gorilla/mux"
"html"
"log"
"net/http"
"time"
)
type Route struct {
Name string
Method string
Pattern string
HandlerFunc ContextHandlerFunc
}
type Context struct {
route *Route
secret string
}
type ContextHandlerFunc func(c *Context, w http.ResponseWriter, r *http.Request)
type Routes []Route
var routes = Routes{
Route{
"Index",
"GET",
"/",
index,
},
}
func wrapLogger(inner ContextHandlerFunc) ContextHandlerFunc {
return func(c *Context, w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner(c, w, r)
log.Printf(
"%s\t%s\t%s\t%s",
r.Method,
r.RequestURI,
c.route.Name,
time.Since(start),
)
}
}
func wrapPanic(inner ContextHandlerFunc) ContextHandlerFunc {
return func(c *Context, w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic caught: %+v", err)
http.Error(w, http.StatusText(500), 500)
}
}()
inner(c, w, r)
}
}
func newRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
context := Context{
&route,
"test",
}
router.Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wrapLogger(wrapPanic(route.HandlerFunc))(&context, w, r)
})
}
return router
}
func index(c *Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q secret is %s\n", html.EscapeString(r.URL.Path), c.secret)
}
func main() {
fmt.Print("Starting\n")
router := newRouter()
log.Fatal(http.ListenAndServe("127.0.0.1:8080", router))
}
I am learning Go and currently in the middle of a nearly identical problem, and this is how I've dealt with it:
First, I think you missed an important detail: There are no global variables in Go. The widest scope you can have for a variable is package scope. The only true globals in Go are predeclared identifiers like true and false (and you can't change these or make your own).
So, it's perfectly fine to set a variable scoped to package main to hold context for your program. Coming from a C/C++ background this took me a little time to get used to. Since the variables are package scoped, they do not suffer from the problems of global variables. If something in another package needs such a variable, you will have to pass it explicitly.
Don't be afraid to use package variables when it makes sense. This can help you reduce complexity in your program, and in a lot of cases make your custom handlers much simpler (where calling http.HandlerFunc() and passing a closure will suffice).
Such a simple handler might look like this:
func simpleHandler(c Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// FIXME Do something with our context
next.ServeHTTP(w, r)
})
}
and be used by:
r = mux.NewRouter()
http.Handle("/", simpleHandler(c, r))
If your needs are more complex, you may need to implement your own http.Handler. Remember that an http.Handler is just an interface which implements ServeHTTP(w http.ResponseWriter, r *http.Request).
This is untested but should get you about 95% of the way there:
package main
import (
"net/http"
)
type complicatedHandler struct {
h http.Handler
opts ComplicatedOptions
}
type ComplicatedOptions struct {
// FIXME All of the variables you want to set for this handler
}
func (m complicatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// FIXME Do stuff before serving page
// Call the next handler
m.h.ServeHTTP(w, r)
// FIXME Do stuff after serving page
}
func ComplicatedHandler(o ComplicatedOptions) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return complicatedHandler{h, o}
}
}
To use it:
r := mux.NewRouter()
// FIXME: Add routes to the mux
opts := ComplicatedOptions{/* FIXME */}
myHandler := ComplicatedHandler(opts)
http.Handle("/", myHandler(r))
For a more developed handler example see basicAuth in goji/httpauth, from which this example was shamelessly ripped off.
Some further reading:
A Recap of Request Handling
Making and Using HTTP Middleware
justinas/alice (for chaining lots of handlers)
I would like to map each route and it's request type (GET, POST, PUT, ...) to generate something like a sitemap.xml in JSON for my restful API.
Goji uses functions to create a new route. I could store the paths and handlers in a map.
My approach would be something like this, except that the compiler gives the following initialization loop error, because sitemap and routes refer to each other (the routemap contains the handler sitemap that should marhsall itself).
main.go:18: initialization loop:
main.go:18 routes refers to
main.go:41 sitemap refers to
main.go:18 routes
Can this be achieved in a more idiomatic way?
package main
import (
"encoding/json"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
var routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
}
type Route struct {
Method string `json:"method"`
Pattern string `json:"pattern"`
Handler web.HandlerType `json:"-"`
}
func NewRoute(method, pattern string, handler web.HandlerType) {
switch method {
case "Get", "get":
goji.DefaultMux.Get(pattern, handler)
case "Post", "post":
goji.DefaultMux.Post(pattern, handler)
// and so on...
}
}
func hello(c web.C, w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}
func sitemap(c web.C, w http.ResponseWriter, r *http.Request) {
// BUG: sitemap tries to marshall itself recursively
resp, _ := json.MarshalIndent(routes, "", " ")
// some error handling...
w.Write(resp)
}
func main() {
for _, r := range routes {
NewRoute(r.Method, r.Pattern, r.Handler)
}
goji.Serve()
}
The easiest way to avoid the initialization loop is to break the loop by delaying one of the initializations.
E.g.:
var routes []Route
func init() {
routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
}
}
With this change your code compiles.
[Edit after chat:]
A fully edited and runnable example that also addresses your question about the switch follows:
package main
import (
"encoding/json"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
var routes []Route
func init() {
// Initialzed in init() to avoid an initialization loop
// since `routes` refers to `sitemap` refers to `routes`.
routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
//Route{"Post", "/somewhereElse", postHandlerExample},
}
}
type Route struct {
Method string `json:"method"`
Pattern string `json:"pattern"`
Handler web.HandlerType `json:"-"`
}
var methods = map[string]func(web.PatternType, web.HandlerType){
"Get": goji.Get,
"Post": goji.Post,
// … others?
}
func (r Route) Add() {
//log.Println("adding", r)
methods[r.Method](r.Pattern, r.Handler)
}
func hello(c web.C, w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}
func sitemap(c web.C, w http.ResponseWriter, r *http.Request) {
resp, err := json.MarshalIndent(routes, "", " ")
if err != nil {
http.Error(w, "Can't generate response properly.", 500)
return
}
w.Write(resp)
}
func main() {
for _, r := range routes {
r.Add()
}
goji.Serve()
}
Available as a gist.
I'll note there is nothing wrong with a switch like you had it,
and in this case if there are only two methods a map may be overkill.
A previous version of the example
didn't use a map and explicitly specified both the function and method name (which were expected to match).
Also this version doesn't check for invalid method names (which if routes is always hard coded and never changed at runtime is reasonable). It would be straight forward to do fn, ok := methods[r.Method] and do something else if/when !ok if desired.
Let's take the following pattern:
package main
import (
"fmt"
"net/http"
)
func main() {
admin := http.NewServeMux()
admin.HandleFunc("/", root)
admin.HandleFunc("/foo", foo)
http.Handle("/admin", admin)
http.ListenAndServe(":4567", nil)
}
func root(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Admin: ROOT")
}
func foo(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Admin: FOO")
}
How is it that when I run /admin, it will fire the root handler, but when I run /admin/foo it won't? Just to be clear, I'm not looking for an alternative package, I actually have a custom router, I'm just generally curious to what's going on here as this pattern isn't making much sense to me.
Like #DewyBroto said, you have to use the full path in the child mux.
You could make a wrapper like this:
func NewChildMux(prefix string, vars ...interface{}) *http.ServeMux {
sm := http.NewServeMux()
for i := 0; i < len(vars); i += 2 {
path := prefix + vars[i].(string)
sm.HandleFunc(path, vars[i+1].(func(http.ResponseWriter, *http.Request)))
}
return sm
}
func main() {
admin := NewChildMux("/admin",
"/", root,
"/foo/", foo,
)
http.Handle("/admin/", admin)
http.ListenAndServe(":4567", nil)
}
Try the following:
func main() {
admin := http.NewServeMux()
// Prefix all paths with the mount point. A ServeMux matches
// the full path, even when invoked from another ServeMux.
mountPoint := "/admin"
admin.HandleFunc(mountPoint, root)
admin.HandleFunc(mountPoint + "/foo", foo)
// Add a trailing "/" to the mount point to indicate a subtree match.
http.Handle(mountPoint + "/", admin)
http.ListenAndServe(":4567", nil)
}
Assuming that we have:
http.HandleFunc("/smth", smthPage)
http.HandleFunc("/", homePage)
User sees a plain "404 page not found" when they try a wrong URL. How can I return a custom page for that case?
Update concerning gorilla/mux
Accepted answer is ok for those using pure net/http package.
If you use gorilla/mux you should use something like this:
func main() {
r := mux.NewRouter()
r.NotFoundHandler = http.HandlerFunc(notFound)
}
And implement func notFound(w http.ResponseWriter, r *http.Request) as you want.
I usually do this:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", homeHandler)
http.HandleFunc("/smth/", smthHandler)
http.ListenAndServe(":12345", nil)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
errorHandler(w, r, http.StatusNotFound)
return
}
fmt.Fprint(w, "welcome home")
}
func smthHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/smth/" {
errorHandler(w, r, http.StatusNotFound)
return
}
fmt.Fprint(w, "welcome smth")
}
func errorHandler(w http.ResponseWriter, r *http.Request, status int) {
w.WriteHeader(status)
if status == http.StatusNotFound {
fmt.Fprint(w, "custom 404")
}
}
Here I've simplified the code to only show custom 404, but I actually do more with this setup: I handle all the HTTP errors with errorHandler, in which I log useful information and send email to myself.
Following is the approach I choose. It is based on a code snippet which I cannot acknowledge since I lost the browser bookmark.
Sample code : (I put it in my main package)
type hijack404 struct {
http.ResponseWriter
R *http.Request
Handle404 func (w http.ResponseWriter, r *http.Request) bool
}
func (h *hijack404) WriteHeader(code int) {
if 404 == code && h.Handle404(h.ResponseWriter, h.R) {
panic(h)
}
h.ResponseWriter.WriteHeader(code)
}
func Handle404(handler http.Handler, handle404 func (w http.ResponseWriter, r *http.Request) bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
hijack := &hijack404{ ResponseWriter:w, R: r, Handle404: handle404 }
defer func() {
if p:=recover(); p!=nil {
if p==hijack {
return
}
panic(p)
}
}()
handler.ServeHTTP(hijack, r)
})
}
func fire404(res http.ResponseWriter, req *http.Request) bool{
fmt.Fprintf(res, "File not found. Please check to see if your URL is correct.");
return true;
}
func main(){
handler_statics := http.StripPrefix("/static/", http.FileServer(http.Dir("/Path_To_My_Static_Files")));
var v_blessed_handler_statics http.Handler = Handle404(handler_statics, fire404);
http.Handle("/static/", v_blessed_handler_statics);
// add other handlers using http.Handle() as necessary
if err := http.ListenAndServe(":8080", nil); err != nil{
log.Fatal("ListenAndServe: ", err);
}
}
Please customize the func fire404 to output your own version of message for error 404.
If you happen to be using Gorilla Mux, you may wish to replace the main function with below :
func main(){
handler_statics := http.StripPrefix("/static/", http.FileServer(http.Dir("/Path_To_My_Static_Files")));
var v_blessed_handler_statics http.Handler = Handle404(handler_statics, fire404);
r := mux.NewRouter();
r.PathPrefix("/static/").Handler(v_blessed_handler_statics);
// add other handlers with r.HandleFunc() if necessary...
http.Handle("/", r);
log.Fatal(http.ListenAndServe(":8080", nil));
}
Please kindly correct the code if it is wrong, since I am only a newbie to Go. Thanks.
Ancient thread, but I just made something to intercept http.ResponseWriter, might be relevant here.
package main
//GAE POC originally inspired by https://thornelabs.net/2017/03/08/use-google-app-engine-and-golang-to-host-a-static-website-with-same-domain-redirects.html
import (
"net/http"
)
func init() {
http.HandleFunc("/", handler)
}
// HeaderWriter is a wrapper around http.ResponseWriter which manipulates headers/content based on upstream response
type HeaderWriter struct {
original http.ResponseWriter
done bool
}
func (hw *HeaderWriter) Header() http.Header {
return hw.original.Header()
}
func (hw *HeaderWriter) Write(b []byte) (int, error) {
if hw.done {
//Silently let caller think they are succeeding in sending their boring 404...
return len(b), nil
}
return hw.original.Write(b)
}
func (hw *HeaderWriter) WriteHeader(s int) {
if hw.done {
//Hmm... I don't think this is needed...
return
}
if s < 400 {
//Set CC header when status is < 400...
//TODO: Use diff header if static extensions
hw.original.Header().Set("Cache-Control", "max-age=60, s-maxage=2592000, public")
}
hw.original.WriteHeader(s)
if s == 404 {
hw.done = true
hw.original.Write([]byte("This be custom 404..."))
}
}
func handler(w http.ResponseWriter, r *http.Request) {
urls := map[string]string{
"/example-post-1.html": "https://example.com/post/example-post-1.html",
"/example-post-2.html": "https://example.com/post/example-post-2.html",
"/example-post-3.html": "https://example.com/post/example-post-3.html",
}
w.Header().Set("Strict-Transport-Security", "max-age=15768000")
//TODO: Put own logic
if value, ok := urls[r.URL.Path]; ok {
http.Redirect(&HeaderWriter{original: w}, r, value, 301)
} else {
http.ServeFile(&HeaderWriter{original: w}, r, "static/"+r.URL.Path)
}
}
i think the clean way is this:
func main() {
http.HandleFunc("/calculator", calculatorHandler)
http.HandleFunc("/history", historyHandler)
http.HandleFunc("/", notFoundHandler)
log.Fatal(http.ListenAndServe(":80", nil))
}
if the address is not /calulator or /history, then it handles notFoundHandler function.
Maybe I'm wrong, but I just checked the sources: http://golang.org/src/pkg/net/http/server.go
It seems like specifying custom NotFound() function is hardly possible: NotFoundHandler() returns a hardcoded function called NotFound().
Probably, you should submit an issue on this.
As a workaround, you can use your "/" handler, which is a fallback if no other handlers were found (as it is the shortest one). So, check is page exists in that handler and return a custom 404 error.
You just need to create your own notFound handler and register it with HandleFunc for the path that you don't handle.
If you want the most control over your routing logic you will need to use a custom server and custom handler type of your own.
http://golang.org/pkg/net/http/#Handler
http://golang.org/pkg/net/http/#Server
This allows you to implement more complex routing logic than the HandleFunc will allow you to do.
you can define
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path != "/" {
writer.WriteHeader(404)
writer.Write([]byte(`not found, da xiong dei !!!`))
return
}
})
when access not found resource, it will execute to http.HandleFunc("/", xxx)
You can simply use something like:
func Handle404(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "404 error\n")
}
func main(){
http.HandleFunc("/", routes.Handle404)
}
If you need to get the standard one, just write:
func main(){
http.HandleFunc("/", http.NotFound)
}
And you'll get:
404 page not found