In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)05/31 Report--
In this article, the editor introduces in detail "how to create RESTful JSON API in Go language". The content is detailed, the steps are clear, and the details are handled properly. I hope this article "how to create RESTful JSON API in Go language" can help you solve your doubts.
What is JSON API?
Before JSON, many websites exchanged data through XML. If you come into contact with JSON after using XML, there is no doubt that you will feel how beautiful the world is. I won't go into the introduction of JSON API here. If you are interested, you can refer to jsonapi.
Basic Web server
Fundamentally speaking, RESTful services are first and foremost Web services. So we can first look at how the basic Web server in the Go language is implemented. The following example implements a simple Web server that responds to the requested URL for any request.
Package mainimport ("fmt"html"log"net/http") func main () {http.HandleFunc ("/", func (w http.ResponseWriter, r * http.Request) {fmt.Fprintf (w, "Hello,% Q", html.EscapeString (r.URL.Path))}) log.Fatal (": 8080", nil))}
The above basic web server uses the two basic functions of the Go standard library, HandleFunc and ListenAndServe.
Func HandleFunc (pattern string, handler func (ResponseWriter, * Request) {DefaultServeMux.HandleFunc (pattern, handler)} func ListenAndServe (addr string, handler Handler) error {server: = & Server {Addr: addr, Handler: handler} return server.ListenAndServe ()}
If you run the basic web service above, you can access http://localhost:8080 directly through the browser.
> go run basic_server.go
Add rout
Although the standard library contains router, I find that many people are confused about how it works. I have used a variety of third-party router libraries in my projects. The most noteworthy is Gorilla Web ToolKit's mux router.
Another popular router is a package called httprouter from Julien Schmidt.
Package mainimport ("fmt"html"log"net/http"github.com/gorilla/mux") func main () {router: = mux.NewRouter (). StrictSlash (true) router.HandleFunc ("/", Index) log.Fatal (http.ListenAndServe (": 8080", router))} func Index (w http.ResponseWriter, r * http.Request) {fmt.Fprintf (w, "Hello,% Q", html.EscapeString (r.URL.Path)}
To run the above code, first use go get to get the source code for mux router:
> go get github.com/gorilla/mux
The above code creates a basic router that assigns an Index processor to the request "/" and executes the Index processor when the client requests http://localhost:8080/.
If you are careful enough, you will find that the previous basic web service access http://localhost:8080/abc responds normally: 'Hello, "/ abc', but after adding routes, you can only access http://localhost:8080. The reason is simple, because we only added the resolution of "/", and the other routes are invalid routes, so they are all 404.
Create some basic routes
Now that we have added routing, we can add more routes.
Suppose we were going to create a basic ToDo application, and our code would look like this:
Package mainimport ("fmt"log"net/http"github.com/gorilla/mux") func main () {router: = mux.NewRouter (). StrictSlash (true) router.HandleFunc ("/", Index) router.HandleFunc ("/ todos", TodoIndex) router.HandleFunc ("/ todos/ {todoId}", TodoShow) log.Fatal (http.ListenAndServe (": 8080", router)} func Index (w http.ResponseWriter, r * http.Request) {fmt.Fprintln (w "Welcome!")} func TodoIndex (w http.ResponseWriter, r * http.Request) {fmt.Fprintln (w, "TodoIndex!")} func TodoShow (w http.ResponseWriter, r * http.Request) {vars: = mux.Vars (r) todoId: = vars ["todoId"] fmt.Fprintln (w, "TodoShow:", todoId)}
Here we add two more routes: todos and todos/ {todoId}.
This is the beginning of RESTful API design.
Notice that the last route is followed by a variable called todoId.
This allows us to pass the id to the route and respond to the request with a specific record.
Basic model
Now that routing is in place, it's time to create a Model that can be used to send and retrieve data using model. In the go language, model can be implemented using structs, while in other languages model is generally implemented using classes.
Package mainimport ("time") type Todo struct {Name string Completed bool Due time.Time} type Todos [] Todo
Above we define a Todo structure to represent items to do. In addition, we define a type Todos, which represents a to-do list, an array, or a fragment.
You will see later that this will become very useful.
Return some JSON
Now that we have the basic model, we can simulate some real responses. We can simulate some static data lists for TodoIndex.
Package mainimport ("encoding/json"fmt"log"net/http"github.com/gorilla/mux") / /... func TodoIndex (w http.ResponseWriter, r * http.Request) {todos: = Todos {Todo {Name: "Write presentation"}, Todo {Name: "Host meetup"},} json.NewEncoder (w) .Encode (todos)} / /.
Now we have created a static Todos shard to respond to client requests. Note that if you request http://localhost:8080/todos, you will get the following response:
[{"Name": "Write presentation", "Completed": false, "Due": "0001-01-01T00:00:00Z"}, {"Name": "Host meetup", "Completed": false, "Due": "0001-01-01T00:00:00Z"}]
Better Model
For experienced veterans, you may have found a problem. Each key that responds to JSON is answered with the first letter, and although it may seem trivial, it is not customary to capitalize the initials of key in response to JSON. So here's how to solve this problem:
Type Todo struct {Name string `json: "name" `Completed bool `json: "completed" `Due time.Time `json: "due" `}
In fact, it is very simple to add tag attributes to the structure, so that you can have complete control over how the structure is marshalled into JSON.
Split code
So far, all our code is in one file. It's messy, and it's time to split the code. We can split the code into the following files according to function.
We are going to create the following file, and then move the corresponding code to the specific code file:
Main.go: program entry file.
Handlers.go: the processor associated with routing.
Routes.go: routing.
Todo.go: code related to todo.
Package mainimport ("encoding/json"fmt"net/http"github.com/gorilla/mux") func Index (w http.ResponseWriter, r * http.Request) {fmt.Fprintln (w, "Welcome!")} func TodoIndex (w http.ResponseWriter, r * http.Request) {todos: = Todos {Todo {Name: "Write presentation"}, Todo {Name: "Host meetup"},} if err: = json.NewEncoder (w) .Encode (todos) Err! = nil {panic (err)}} func TodoShow (w http.ResponseWriter, r * http.Request) {vars: = mux.Vars (r) todoId: = vars ["todoId"] fmt.Fprintln (w, "Todo show:" TodoId)} package mainimport ("net/http"github.com/gorilla/mux") type Route struct {Name string Method string Pattern string HandlerFunc http.HandlerFunc} type Routes [] Routefunc NewRouter () * mux.Router {router: = mux.NewRouter () .StrictSlash (true) for _, route: = range routes {router. Methods (route.Method). Path (route.Pattern). Name (route.Name). Handler (route.HandlerFunc)} return router} var routes = Routes {Route {"Index", "GET", "/", Index,}, Route {"TodoIndex", "GET", "/ todos", TodoIndex,}, Route {"TodoShow", "GET", "/ todos/ {todoId}", TodoShow,} } package mainimport "time" type Todo struct {Name string `json: "name" `Completed bool `json: "completed" `Due time.Time `json: "due" `} type Todos [] Todopackage mainimport ("log"net/http") func main () {router: = NewRouter () log.Fatal (http.ListenAndServe (": 8080", router))}
Better Routing
In the process of refactoring, we created a more functional routes file. This new file takes advantage of a structure that contains multiple routing information. Note that here we can specify the type of request, such as GET, POST, DELETE, and so on.
Output Web log
I also have an ulterior motive in the split routing file. As you'll see later, it's easy to decorate the http processor with another function after the split.
First of all, we need the ability to log web requests, just like many popular web servers. In the GE language, there is no web log package or function in the standard library, so we need to create it ourselves.
Package loggerimport ("log"net/http"time") func Logger (inner http.Handler, name string) 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", r.Method, r.RequestURI, name, time.Since (start) )})}
Above we define a Logger function that can wrap handler.
This is a very standard idiom in the Go language. In fact, it is also the usual way of functional programming. Very efficient, we just need to pass Handler into the function, and then it wraps the incoming handler to add web logging and time-consuming statistics.
Apply the Logger modifier
To apply the Logger modifier, we can create a router. We simply wrap all our current routes into it, and modify the NewRouter function as follows:
Func NewRouter () * mux.Router {router: = mux.NewRouter () .StrictSlash (true) for _, route: = range routes {var handler http.Handler handler = route.HandlerFunc handler = Logger (handler, route.Name) router Methods (route.Method). Path (route.Pattern). Name (route.Name). Handler (handler)} return router}
Now run our program again, and we can see that the log looks like this:
2014-11-19 12:41:39 GET / todos TodoIndex 148.324us
This routing file is crazy. Let's reconstruct it.
The routing routes file has now become a little larger, so let's break it down into multiple files:
Routes.go
Router.go
Package mainimport "net/http" type Route struct {Name string Method string Pattern string HandlerFunc http.HandlerFunc} type Routes [] Routevar routes = Routes {Route {"Index", "GET", "/", Index,}, Route {"TodoIndex", "GET", "/ todos", TodoIndex,}, Route {"TodoShow", "GET", "/ todos/ {todoId}", TodoShow,} } package mainimport ("net/http"github.com/gorilla/mux") func NewRouter () * mux.Router {router: = mux.NewRouter () .StrictSlash (true) for _, route: = range routes {var handler http.Handler handler = route.HandlerFunc handler = Logger (handler, route.Name) router. Methods (route.Method). Path (route.Pattern). Name (route.Name). Handler (handler)} return router}
In addition, take some more responsibilities.
So far, we have some pretty good boilerplate code (boilerplate), and it's time to take a fresh look at our processors. We need a little more responsibility. First modify the TodoIndex to add the following two lines of code:
Func TodoIndex (w http.ResponseWriter, r * http.Request) {todos: = Todos {Name: "Write presentation"}, Todo {Name: "Host meetup"},} w.Header (). Set ("Content-Type", "application/json; charset=UTF-8") w.WriteHeader (http.StatusOK) if err: = json.NewEncoder (w) .Encode (todos); err! = nil {panic (err)}}
Two things happened here. First, we set the response type and tell the client that we expect to accept JSON. Second, we explicitly set the response status code.
The net/http server in the Go language will try to guess the output content type for us (though not always exactly), but now that we know exactly the response type, we should always set it ourselves.
Just a moment. Where is our database?
Obviously, if we are going to create a RESTful API, we need some places to store and retrieve data. However, this is beyond the scope of this article, so we will simply create a very crude mock database (non-thread safe).
Let's create a repo.go file with the following contents:
Package mainimport "fmt" var currentId intvar todos Todos// Give us some seed datafunc init () {RepoCreateTodo (Todo {Name: "Write presentation"}) RepoCreateTodo (Todo {Name: "Host meetup"})} func RepoFindTodo (id int) Todo {for _, t: = range todos {if t.Id = = id {return t}} / / return empty Todo if not found return Todo {} func RepoCreateTodo (t Todo) Todo {currentId + = 1 t.Id = currentId todos = append (todos T) return t} func RepoDestroyTodo (id int) error {for I, t: = range todos {if t.Id = = id {todos = append (todos [: I], Todos [I + 1:]...) Return nil}} return fmt.Errorf ("Could not find Todo with id of% d to delete", id)}
Add ID to Todo
We have created a mock database, and we use and give id, so we need to update our Todo structure accordingly.
Package mainimport "time" type Todo struct {Id int `json: "id" `Name string `json: "name" `Completed bool `json: "completed" `Due time.Time `json: "due" `} type Todos [] Todo
Update our TodoIndex
To use the database, we need to retrieve the data in TodoIndex. Modify the code as follows:
Func TodoIndex (w http.ResponseWriter, r * http.Request) {w.Header () .Set ("Content-Type", "application/json; charset=UTF-8") w.WriteHeader (http.StatusOK) if err: = json.NewEncoder (w) .Encode (todos); err! = nil {panic (err)}}
POST JSON
So far, we've just output JSON, and now it's time to go in and store some JSON.
Add the following routes to the routes.go file:
Route {"TodoCreate", "POST", "/ todos", TodoCreate,}
Create routing
Func TodoCreate (w http.ResponseWriter, r * http.Request) {var todo Todo body, err: = ioutil.ReadAll (io.LimitReader (r.Body, 1048576)) if err! = nil {panic (err)} if err: = r.Body.Close (); err! = nil {panic (err)} if err: = json.Unmarshal (body, & todo); err! = nil {w.Header (). Set ("Content-Type", "application/json") Charset=UTF-8 ") w.WriteHeader (422) / / unprocessable entity if err: = json.NewEncoder (w) .Encode (err); err! = nil {panic (err)} t: = RepoCreateTodo (todo) w.Header (). Set (" Content-Type "," application/json; charset=UTF-8 ") w.WriteHeader (http.StatusCreated) if err: = json.NewEncoder (w) .Encode (t); err! = nil {panic (err)}}
First we open the requested body. Notice that we use io.LimitReader. This is a good way to protect the server from malicious attacks. Suppose someone wants to send 500GB's JSON to your server?
After we read the body, we deconstruct the Todo structure. If it fails, we respond correctly, using the appropriate response code 422, but we still respond back with json. This allows the client to understand that an error has occurred, and there is a way to know exactly what happened.
Finally, if everything is passed, we respond to the 201 status code, indicating that the requested entity has been successfully created. We also respond to the json that represents the entity we created, which will contain an id that the client may need to use next.
POST, some JSON.
Now that we have pseudo-repo and create routing, we need to post some data. We use curl to do this with the following command:
The copy code is as follows:
Curl-H "Content-Type: application/json"-d'{"name": "New Todo"} 'http://localhost:8080/todos
If you visit through http://localhost:8080/todos again, you will probably get the following response:
[{"id": 1, "name": "Write presentation", "completed": false, "due": "0001-01-01T00:00:00Z"}, {"id": 2, "name": "Host meetup", "completed": false, "due": "0001-01-01T00:00:00Z"}, {"id": 3, "name": "New Todo" "completed": false, "due": "0001-01-01T00:00:00Z"]
What we haven't done yet.
Although we have made a good start, there are still many things to do:
Version control: what if we need to modify the API and the result changes completely? Maybe we need to add / v1/prefix at the beginning of our route?
Authorization: unless these are public / free API, we may also need authorization. It is recommended to learn something about JSON web tokens.
ETag-if you are building something that needs to be extended, you may need to implement eTag.
What else?
For all projects, it was small at first, but it soon got out of control. But if we want to take it to another level and get it ready for production, there are a few extra things that need to be done:
Massive refactoring (refactoring).
Create several packages for these files, such as some JSON helpers, modifiers, processors, and so on.
Testing, so that you can't forget that. We didn't do any tests here. Testing is necessary for production systems.
After reading this, the article "how to create RESTful JSON API in Go language" has been introduced. If you want to master the knowledge points of this article, you still need to practice and use it yourself. If you want to know more about related articles, welcome to follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.