Google GO: routing request handling mystical declarations? - google-app-engine

I'm dorking around with Google GO for the first time. I've extended the "hello world" application to try to have paths defined in the init section. Here's what I've done so far:
package hello
import (
"fmt"
"net/http"
)
func init() {
http.HandleFunc("/service", serviceHandler)
http.HandleFunc("/site", siteHandler)
http.HandleFunc("/", handler)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, there")
}
func serviceHandler( w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "this is Services")
}
func siteHandler( w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "this is Sites")
}
Only the handler() callback is ever executed -- the others are ignored. E.g.: http://myserver/service/foo prints Hello, there. I had hoped that it would be this is Services.
Is there a better way to do service routing? Ideally, I would expect these to be separate scripts anyway, but it looks like Go has only one script, based on the fact that the app.yaml requires a special string _go_app in the script declaration.
Thanks!

According to the documentation at: http://golang.org/pkg/net/http/#ServeMux
path specs that do not have a trailing slash only match that path exactly. Add a slash to the end like so: http.HandleFunc("/service/", serviceHandler) and it will work as you expect.

Related

Go HTTP Server for React, Angular, etc

I created this small HTTP Server in GO for static files:
func wrapHandler(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(srw, r)
log.Printf("GET %s", r.RequestURI)
}
}
func main() {
http.HandleFunc("/", wrapHandler(
http.FileServer(http.Dir("/static"))
))
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
It works perfectly with React and Angular dist files (after transpile them). However if I already selected a route e.g. http://example.org:8080/customers and click on refresh in the browser I got a 404 page not found. That is the only situation where my code is failing.
It happens because on React and Angular index.html acts as a front controller and can handle the routes. However to make it working I need to internally redirect all not found requests to index.html.
Since is the angular/react handling the route I wouldn't like to create a http.HandleFunc for each route created in the angular/react. I would like to something similar to express :
app.use(express.static(path.join(__dirname, 'static')));
app.use("*",function(req,res){
res.sendFile(path.join(__dirname, 'static/index.html'));
});
or NGINX:
try_files $uri $uri/ index.html;
Any clues on how to do it in go?
It's important to understand how Go's http package handles route matching as it's a bit different from other languages/frameworks. HandleFunc uses ServeMux under the hood and (from the docs):
ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL. [emphasis mine]
Given this behavior, I would recommend creating explicit handlers for each folder in static (e.g. css/, js/), or putting all in a single subfolder of static, and then respond with your index.html file for all other requests (using the root route (/)). This works because requests with routes prefixed by /css/ or /js/ will more closely match the appropriate static handler routes, while all others will not and therefore only most closely match the root route. You just need to be sure not to create conflicting routes on your front-end.
This way any request explicitly for CSS/JS/image assets will be handled by serving the static directory and all other requests will be responded to with your React app.
Here's an example (leaving out your wrapHandler for simplicity):
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/index.html")
})
http.Handle("/js/", http.FileServer(http.Dir("static")))
http.Handle("/css/", http.FileServer(http.Dir("static")))
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
or if you want to be a bit more explicit:
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/index.html")
})
jsFs := http.FileServer(http.Dir("static/js"))
http.Handle("/js/", http.StripPrefix("/js", jsFs))
cssFs := http.FileServer(http.Dir("static/css"))
http.Handle("/css/", http.StripPrefix("/css", cssFs))
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
My website works pretty much the same way (using Vue instead of React).
An Improvement
As mentioned above, you might consider putting all you static assets in a sub folder of your current static/ folder. Consider structuring your files like this:
public/
index.html
static/
css/
js/
img/
Which is the default file structure for built React apps (but public is called build by default).
This will let you use the above approach in a more streamlined way since you'll only need one fileserver handler for all static assets. Then you can use the following code:
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "public/index.html")
})
fs := http.FileServer(http.Dir("public/static/"))
http.Handle("/static/", http.StripPrefix("/static", fs))
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}

How to set up a route to serve reactjs app?

I'm trying to set up a route to serve my reactjs application.
I have my index.html and bundle.js in the public folder
/public/index.html
/public/bundle.js
I'm using go as my backend API, and also to serve my reactjs app.
I created a subroute for my app like:
r := mux.NewRouter()
app := r.Host("app.example.com").Subrouter()
So any request with app as the subdomain will be for my Reactjs app.
So now I have to serve every request regardless of the URL to my reactjs app.
Is path prefix what I need here?
I tried this:
app.PathPrefix("/").Handler(serveReact)
func serveReact(w http.ResponseWriter, r *http.Request) {
}
But I get this error:
cannot use serveReact (type func() http.Handler) as type http.Handler
in argument to app.PathPrefix("/").Handler: func() http.Handler does
not implement http.Handler (missing ServeHTTP method)
Your http handler needs a ServeHTTP method. If you pass your function to http.HandlerFunc, that will be introduced for you:
app.PathPrefix("/").Handler(http.HandlerFunc(serveReact))
func serveReact(w http.ResponseWriter, r *http.Request) {
}
The HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.
source
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
Similarly, you could use the mux router HandlerFunc instead:
app.PathPrefix("/").HandlerFunc(serveReact)
func serveReact(w http.ResponseWriter, r *http.Request) {
}
This essentially performs both steps for you in a combined single step.

value to be passed to the fileName of appengine/file.Delete () is?

I want to know is fileName to pass when you delete a file of gcs from gae/go.
Although passed "/gs/{bucketname}/{filename}", error message "RPC error UNKNOWN_ERROR:" is returned
package main
import (
"appengine"
"appengine/file"
"net/http"
)
func handle(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
file.Delete(c, "/gs/{bucketname}/{filename}")
}
I'm not an expert on Go but to work with Cloud Storage you need the Google Cloud Storage Go Client Library as indicated here [1].
Take a look at this sample code [2], more specifically to the deleteFiles() fuction. You can see that the function to the delete files is DeleteObject [3].
[1] https://cloud.google.com/appengine/docs/go/storage#google_cloud_storage
[2] https://cloud.google.com/appengine/docs/go/googlecloudstorageclient/getstarted
[3] http://godoc.org/google.golang.org/cloud/storage#DeleteObject

Go + Angular ui-router

I'm a new Gopher trying to do a Go backend to serve my Angularjs frontend and also serve an API.
This is what I have so far.
package main
import (
"github.com/gorilla/mux"
"log"
"net/http"
)
func main() {
rtr := mux.NewRouter()
srtr := rtr.PathPrefix("/api").Subrouter()
srtr.HandleFunc("/hello", hello).Methods("GET")
rtr.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))
http.Handle("/", rtr)
log.Println("Listening...")
http.ListenAndServe(":3000", nil)
}
func hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
}
Everything works fine. /api/hello return "Hello World" and if I go to / it will serve my index.html. However since I'm trying to use angular ui-router so I need my go server to send all non-registered routes to angular so angular ui-router can handle them.
For example: If I go /random right now it will return a 404 since I don't have any file under ./static named random. But what I want is Go to forward that request to Angular so ui-router can handle the /random
In Your router You should serve index.html to all undefined elsewhere URLs. In mux package there is helpful handler:
http://www.gorillatoolkit.org/pkg/mux#Router
- look at NotFoundHandler
You can use it, to handle all 404's and serve index.html instead:
func main() {
r := mux.NewRouter()
r.HandleFunc("/foo", fooHandler)
r.NotFoundHandler = http.HandlerFunc(notFound)
http.Handle("/", r)
}
and define notFound function:
func notFound(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/index.html")
}

How to send 204 No Content with Go http package?

I built a tiny sample app with Go on Google App Engine that sends string responses when different URLs are invoked. But how can I use Go's http package to send a 204 No Content response to clients?
package hello
import (
"fmt"
"net/http"
"appengine"
"appengine/memcache"
)
func init() {
http.HandleFunc("/", hello)
http.HandleFunc("/hits", showHits)
}
func hello(w http.ResponseWriter, r *http.Request) {
name := r.Header.Get("name")
fmt.Fprintf(w, "Hello %s!", name)
}
func showHits(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%d", hits(r))
}
func hits(r *http.Request) uint64 {
c := appengine.NewContext(r)
newValue, _ := memcache.Increment(c, "hits", 1, 0)
return newValue
}
According to the package docs:
func NoContent(w http.ResponseWriter, r *http.Request) {
// Set up any headers you want here.
w.WriteHeader(http.StatusNoContent) // send the headers with a 204 response code.
}
will send a 204 status to the client.
Sending 204 response from your script means your instance still need to run and cost you money. If you are looking for a caching solution. Google got it and it's called Edge Cache.
You only need to response with the following headers and Google will automatically cache your response in multiple servers nearest to the users (that is, replying with 204). This greatly enhance your site's speed and reduce instance cost.
w.Header().Set("Cache-Control", "public, max-age=86400")
w.Header().Set("Pragma", "Public")
You can adjust the max-age, but do it wisely.
By the way, it seems billing must be enabled in order to use Edge Cache

Resources