Simplifying route handler to handle parameters without framework - go

In rocket.rs, we've this simple route code:
#[get("/hello/<name>/<age>")]
fn hello(name: &str, age: u8) -> String {
format!("Hello, {} year old named {}!", age, name)
}
where if you were to visit http://localhost:8000/hello/John/58 in the browser, you’d see:
Hello, 58 year old named John!
I read this, but the accepted answer is about a way to do Go url parameters mapping for single route, that could read http://localhost:8080/blob/123/test as /blob/{id}/test and display the required route.
I know there are some great routers/frameworks there, but looking to build simple code myself to understand http route handlers in a better way.
Let's say I've:
type Tender struct {
tenderReference string
venderCode int
}
func (t Tender) readWrite() {
fmt.Printf("Tender %s is ready for vendor %d to review and submit\n", t.tenderReference, t.venderCode)
}
func (t Tender) readOnly(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Tender %s already submitted by vender %d\n", t.tenderReference, t.venderCode)
}
And want my routes to be something like:
/api/tender/readWrite/{tenderReference}/vendor/{venderCode} that is calling func (t Tender) readWrite(){}
/api/tender/readOnly/{tenderReference}/vendor/{venderCode} that is calling func (t Tender) readOnly(){}
How many route handler do I have to build?

I solved it as below, other thoughts are welcomed:
404.go
package main
import (
"fmt"
"net/http"
)
func handle404(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "mmm, it looks you are playing around, page is not available :)\n")
}
getField.go
package main
import "net/http"
type ctxKey struct{}
func getField(r *http.Request, index int) string {
fields := r.Context().Value(ctxKey{}).([]string)
return fields[index]
}
routes.go
package main
import (
"net/http"
"regexp"
)
type route struct {
method string
regex *regexp.Regexp
handler http.HandlerFunc
}
var routes = []route{
newRoute("GET", "/api/tender/(rw|r)/([^/]+)/vendor/([0-9]+)", apiTenders),
}
func newRoute(method, pattern string, handler http.HandlerFunc) route {
return route{method, regexp.MustCompile("^" + pattern + "$"), handler}
}
tendor.go
package main
import (
"fmt"
"net/http"
"strconv"
)
type Tender struct {
tenderReference string
venderCode int
}
// Handles GET /api/tender/(rw|r)/([^/]+)/vendor/([0-9]+)
func apiTenders(w http.ResponseWriter, r *http.Request) {
action := getField(r, 0)
tenderReference := getField(r, 1)
venderCode, _ := strconv.Atoi(getField(r, 2))
tender := Tender{tenderReference, venderCode}
switch action {
case "rw":
tender.readWrite(w, r) // Display tender and allow vendor to submit feedback
case "r":
tender.readOnly(w, r) // Display readOnly copy of the tender
default:
fmt.Fprintf(w, "Tendert ERROR\n")
}
}
func (t Tender) readWrite(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Tender %s is ready for vendor %d to review and submit\n", t.tenderReference, t.venderCode)
}
func (t Tender) readOnly(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Tender %s already submitted by vender %d\n", t.tenderReference, t.venderCode)
}
server.go
package main
import (
"context"
"fmt"
"net/http"
"strings"
)
type apiHandler struct{}
func main() {
http.Handle("/api/", apiHandler{})
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
// The "/" pattern matches everything, so we need to check
// that we're at the root here.
if req.URL.Path != "/" {
http.NotFound(w, req)
return
}
fmt.Fprintf(w, "Welcome to the home page!")
})
http.ListenAndServe(":8000", nil)
}
func (apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var allow []string
for _, route := range routes {
matches := route.regex.FindStringSubmatch(r.URL.Path)
if len(matches) > 0 {
if r.Method != route.method {
allow = append(allow, route.method)
continue
}
ctx := context.WithValue(r.Context(), ctxKey{}, matches[1:])
route.handler(w, r.WithContext(ctx))
return
}
}
if len(allow) > 0 {
w.Header().Set("Allow", strings.Join(allow, ", "))
http.Error(w, "405 method not allowed", http.StatusMethodNotAllowed)
return
}
handle404(w, r)
//http.NotFound(w, r)
}

Related

How to print statuscode using response.statusCode in Visual Studio Code using Go language

I'm currently doing this tutorial from YouTube : https://www.youtube.com/watch?v=-Sol_RMG_fo&ab_channel=dbestech in the tutorial I can't get the data from back end to flutter, so I asked the YouTuber what should I do?, and he tells me to print statuscode using response.statusCode, but really don't know what I have to do
Here's the code:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
type Tasks struct {
ID string `json:"id"`
TaskName string `json:"task_name"`
TaskDetail string `json:"detail"`
Date string `json:"date"`
}
var tasks []Tasks
func allTasks() {
task := Tasks{
ID: "1",
TaskName: "New Projects",
TaskDetail: "You must lead the project and finish it",
Date: "2022-01-22"}
tasks = append(tasks, task)
task1 := Tasks{
ID: "2",
TaskName: "Power Project",
TaskDetail: "We need to hire more stuffs before the deadline",
Date: "2022-01-22"}
tasks = append(tasks, task1)
fmt.Println("your tasks are", tasks)
}
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Println("I am home page")
}
func getTasks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/")
json.NewEncoder(w).Encode(tasks)
}
func getTask(w http.ResponseWriter, r *http.Request) {
taskId := mux.Vars(r)
flag := false
for i := 0; i < len(tasks); i++ {
if taskId["id"] == tasks[i].ID {
json.NewEncoder(w).Encode(tasks[i])
flag = true
break
}
}
if flag == false {
json.NewEncoder(w).Encode(map[string]string{"status": "Error"})
}
}
func createTask(w http.ResponseWriter, r *http.Request) {
fmt.Println("I am home page")
}
func deleteTask(w http.ResponseWriter, r *http.Request) {
fmt.Println("I am home page")
}
func updateTask(w http.ResponseWriter, r *http.Request) {
fmt.Println("I am home page")
}
func handleRoutes() {
router := mux.NewRouter()
router.HandleFunc("/", homePage).Methods("GET")
router.HandleFunc("/gettasks", getTasks).Methods("GET")
router.HandleFunc("/gettask/{id}", getTask).Methods("GET")
router.HandleFunc("/create", createTask).Methods("POST")
router.HandleFunc("/delete/{id}", deleteTask).Methods("DELETE")
router.HandleFunc("/update/{id}", updateTask).Methods("PUT")
log.Fatal(http.ListenAndServe(":3306", router))
}
func main() {
allTasks()
fmt.Println("Hello Flutter boys")
handleRoutes()
}
I think he said you to print the statuscode from flutter side. Then based on that statuscode you can understand where was the issue.
I have tested your code in local using postman and I am getting data.

How do I use Twitter API with github.com/ChimeraCoder/anaconda?

I have a web API written Go, I would like to use it to consume the Twitter API and return some properties from a user's timeline.
I have added https://github.com/ChimeraCoder/anaconda to my web API, however I cannot understand from the docs just how to get a timeline for a user.
This is my application currently.
I am trying to use the FeedHandler method. I can trigger a search, but when it comes to actually returning a user's timeline, I am stuck.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"github.com/ChimeraCoder/anaconda"
"github.com/gorilla/mux"
)
type Meta struct {
Description string `json:"description"`
Version string `json:"version"`
Bugs GitHubLink `json:"bugs"`
}
type GitHubLink struct {
Url string `json:"url"`
}
type ErrorMessage struct {
Status int `json:"status"`
Message string `json:"message"`
}
var (
consumerKey = getenv("TWITTER_CONSUMER_KEY")
consumerSecret = getenv("TWITTER_CONSUMER_SECRET")
)
func getenv(name string) string {
v := os.Getenv(name)
if v == "" {
panic("required environment variable " + name + "is missing")
}
return v
}
func main() {
anaconda.SetConsumerKey(consumerKey)
anaconda.SetConsumerSecret(consumerSecret)
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/feed/{screenName}", FeedHandler)
r.HandleFunc("/healthz", HealthzHandler)
r.NotFoundHandler = http.HandlerFunc(NotFoundHandler)
log.Fatal(http.ListenAndServe(":8000", r))
}
func HomeHandler(w http.ResponseWriter, r *http.Request) {
meta := Meta{
Description: "API returning twitter.com posted images",
Version: "0.0.0",
Bugs: GitHubLink{Url: "https://foo.bar"}}
json.NewEncoder(w).Encode(meta)
}
func FeedHandler(w http.ResponseWriter, r *http.Request) {
api := anaconda.NewTwitterApi("", "")
// vars := mux.Vars(r)
tweets := api.GetUserTimeline('<users_screen_name>')
// searchResult, _ := api.
// for _, tweet := range searchResult.Statuses {
// fmt.Println(tweet.Text)
// }
fmt.Fprintf(w, "Not Implemented Yet")
}
func HealthzHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, max-age=0")
}
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
errorMessage := ErrorMessage{Status: 404, Message: "Request resource does not exist"}
json.NewEncoder(w).Encode(errorMessage)
}
You need to pass in the user's screen name as a property on url.Values{}
func FeedHandler(w http.ResponseWriter, r *http.Request) {
api := anaconda.NewTwitterApi("", "")
v := url.Values{}
v.Set("screen_name", "some_user_name")
searchResult, _ := api.GetUserTimeline(v)
json.NewEncoder(w).Encode(searchResult)
}

Struct value been stored in every request Golang using net/http

I am new to Golang, I am testing the net/http to run some path but I got some problem that I don't understand.
Here is my codes.
package main
import (
"fmt"
"net/http"
)
type Content struct {
Data map[interface{}]interface{}
}
func main() {
mux := http.NewServeMux()
mux.Handle("/favicon.ico", http.NotFoundHandler())
mux.HandleFunc("/", Index)
mux.HandleFunc("/test", Testhandler)
http.ListenAndServe(":8080", mux)
}
func Index(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
fmt.Println("404");
return
}
fmt.Println("index content ", Content)
}
func Testhandler(w http.ResponseWriter, r *http.Request) {
data := make(map[interface{}]interface{})
data["data1"] = "data 1 content"
data["data2"] = "data 2 content"
Content.Data = data
fmt.Println("test content ", Content)
}
So, if I go to index http://localhost:8080/, I got empty content index content {{false } map[]} ,
And I goto http://localhost:8080/test I got the content correctly , test content {{false } map[data1:data 1 content data2:data 2 content]},
But when I go back to index http://localhost:8080/ there already content there index content {{false } map[data1:data 1 content data2:data 2 content]},
So question here, why am I not getting the empty struct content when I back to the index? I thought the struct will be in initial state with every single request? The http should be stateless, right?
What you are probably experiencing is the result of this code or something similar (your code does not compile):
package main
import (
"fmt"
"net/http"
)
var Content struct {
Data map[interface{}]interface{}
}
func main() {
mux := http.NewServeMux()
mux.Handle("/favicon.ico", http.NotFoundHandler())
mux.HandleFunc("/", Index)
mux.HandleFunc("/test", Testhandler)
http.ListenAndServe(":8080", mux)
}
func Index(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
fmt.Println("404")
return
}
fmt.Println("index content ", Content)
}
func Testhandler(w http.ResponseWriter, r *http.Request) {
data := make(map[interface{}]interface{})
data["data1"] = "data 1 content"
data["data2"] = "data 2 content"
Content.Data = data
fmt.Println("test content ", Content)
}
Solution
With this you are creating a global variable Content that keeps its state across calls to the webserver. What you probably intended is this:
package main
import (
"fmt"
"net/http"
)
type Content struct {
Data map[interface{}]interface{}
}
func main() {
mux := http.NewServeMux()
mux.Handle("/favicon.ico", http.NotFoundHandler())
mux.HandleFunc("/", Index)
mux.HandleFunc("/test", Testhandler)
http.ListenAndServe(":8080", mux)
}
func Index(w http.ResponseWriter, r *http.Request) {
var c Content
if r.URL.Path != "/" {
fmt.Println("404")
return
}
fmt.Println("index content ", c)
}
func Testhandler(w http.ResponseWriter, r *http.Request) {
var c Content
data := make(map[interface{}]interface{})
data["data1"] = "data 1 content"
data["data2"] = "data 2 content"
c.Data = data
fmt.Println("test content ", c)
}
Changes made
make Content a type as you already did in your sample (that way it is not a global variable any more but defining a type we can reuse)
declare Content in each call where it is needed (not globally as we do not want it to keep its content across server calls)
Essence
You cannot use a type without declaring a variable from it first. That is why your sample did not build. If you try go will complain that Content is not an expression.

Extending GoLang's http.ResponseWriter functionality to pre/post process responses

I am trying to write a simple http MiddleWare handler that will process an http response. Unfortunately, it does not work and I cannot figure out what mistake I am making. Any/all help is appreciated!
I am using Go Gorilla mux router
Here are illustrative parts of the code:
import (
"fmt"
"log"
"github.com/gorilla/mux"
)
:
func Start() {
router := mux.NewRouter()
router.HandleFunc("/", myHandler)
:
log.Fatal(http.ListenAndServe(":8088", Middleware(router)))
}
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "myHandler called")
}
func Middleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
neww := NewProcessor(w)
h.ServeHTTP(neww, r)
})
}
type Processor struct {
http.ResponseWriter
}
func (r *Processor) Write(b []byte) (int, error) {
fmt.Printf("******* Processor writing...")
log.Print(string(b)) // log it out
return r.Write(b) // pass it to the original ResponseWriter
}
func NewProcessor(w http.ResponseWriter) http.ResponseWriter {
fmt.Printf("******* Creating new Processor...")
return &Processor{ResponseWriter: w}
}
The output I get is listed below (extra logging text omitted for clarity):
******* Creating new Processor
myHandler called
However, notice the message "******* Processor writing..." was not displayed, suggesting that the "Write" function did not get called.
What changes need to be made to allow the "Write" function to be called?
return r.Write(b) caused an infinite loop of calls to the Processor's Write() method. Replacing it with return r.ResponseWriter.Write(b) fixed the bug.
Here is the corrected code:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", myHandler)
log.Fatal(http.ListenAndServe(":8088", Middleware(mux)))
}
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "myHandler called")
}
func Middleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
new := NewProcessor(w)
h.ServeHTTP(new, r)
})
}
type Processor struct {
http.ResponseWriter
}
func (r *Processor) Write(b []byte) (int, error) {
log.Print("******* Processor writing...")
log.Print(string(b)) // log it out
return r.ResponseWriter.Write(b) // pass it to the original ResponseWriter
}
func NewProcessor(w http.ResponseWriter) http.ResponseWriter {
log.Print("******* Creating new Processor...")
return &Processor{ResponseWriter: w}
}
Output:
2016/07/21 22:59:08 ******* Creating new Processor...
2016/07/21 22:59:08 ******* Processor writing...
2016/07/21 22:59:08 myHandler called

http HandleFunc argument in golang

I want to use a rate limiting or throttler library to limit the number of client requests. I use a vendor library in my code base. I want to pass in a ResponseWriter, Request and a third variable retrieved from the URL. When I use the library for throttling, it gives me back a handler that only handles two arguments. How can I pass my third argument into the handler?
Here is my current code:
package main
import (
"fmt"
"github.com/didip/tollbooth"
"net/http"
)
func viewHandler(
w http.ResponseWriter,
r *http.Request,
uniqueId string,
) {
//data := getData(uniqueId)
fmt.Println("Id:", uniqueId)
p := &objects.ModelApp{LoggedUser: "Ryan Hardy", ViewData: "data"}
renderTemplate(w, "view", p)
}
//URL validation for basic web services
var validPath = regexp.MustCompile("^/$|/(home|about|view)/(|[a-zA-Z0-9]+)$")
func makeHandler(
fn func(
http.ResponseWriter,
*http.Request, string,
)) http.HandlerFunc {
return func(
w http.ResponseWriter,
r *http.Request,
) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil {
http.NotFound(w, r)
return
}
fn(w, r, m[2])
}
}
func main() {
http.Handle("/view/", makeHandler(tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), viewHandler)))
http.ListenAndServe(":8080", nil)
}
Could anyone help me with this?
I'm on my phone so this may be difficult to type but you could use the http.Handle function which takes an interface of Handler something like
type makeHandler struct {
YourVariable string
}
func (m *makeHandler) ServeHTTP (w http.ResponseWriter, r *http.Request) {
yourVariableYouNeed := m.YourVariable
// do whatever
w.Write()
}
// do whatever you need to get your variable
blah := &makeHandler{ yourThing }
http.Handle("/views", blah)
On my phone so can't test but it should work, let me know if it doesn't.

Resources