A JSON API server

Armed with the information from the last section, it is possible to use the HTTP package to create services over HTTP. Earlier we discussed the perils of creating services using raw TCP directly when we created a server for our global monetary currency service. In this section, we explore how to create an API server for the same service using HTTP as the underlying protocol. The new HTTP-based service has the following design goals:

  • Use HTTP as the transport protocol
  • Use JSON for structured communication between client and server
  • Clients query the server for currency information using JSON-formatted requests
  • The server respond using JSON-formatted responses

The following shows the code involved in the implementation of the new service. This time, the server will use the curr1 package (see github.com/vladimirvivien/learning-go /ch11/curr1) to load and query ISO 4217 currency data from a local CSV file.

The code in the curr1 package defines two types, CurrencyRequest and Currency, intended to represent the client request and currency data returned by the server, respectively as listed here:

type Currency struct { 
   Code    string `json:"currency_code"` 
   Name    string `json:"currency_name"` 
   Number  string `json:"currency_number"` 
   Country string `json:"currency_country"` 
} 
 
type CurrencyRequest struct { 
   Get   string `json:"get"` 
   Limit int    `json:limit` 
} 

golang.fyi/ch11/curr1/currency.go

Note that the preceding struct types shown are annotated with tags that describe the JSON properties for each field. This information is used by the JSON encoder to encode the key name of JSON objects (see Chapter 10, Data IO in Go, for detail on encoding). The remainder of the code, listed in the following snippet, defines the functions that set up the server and the handler function for incoming requests:

import ( 
   "encoding/json" 
   "fmt" 
   "net/http" 
 
   " github.com/vladimirvivien/learning-go/ch11/curr1" 
) 
var currencies = curr1.Load("./data.csv") 
 
func currs(resp http.ResponseWriter, req *http.Request) { 
   var currRequest curr1.CurrencyRequest 
   dec := json.NewDecoder(req.Body) 
   if err := dec.Decode(&currRequest); err != nil { 
         resp.WriteHeader(http.StatusBadRequest) 
         fmt.Println(err) 
         return 
   } 
 
   result := curr1.Find(currencies, currRequest.Get) 
   enc := json.NewEncoder(resp) 
   if err := enc.Encode(&result); err != nil { 
         fmt.Println(err) 
         resp.WriteHeader(http.StatusInternalServerError) 
         return 
   } 
} 
 
func main() { 
   mux := http.NewServeMux() 
   mux.HandleFunc("/currency", get) 
 
   if err := http.ListenAndServe(":4040", mux); err != nil { 
         fmt.Println(err) 
   } 
} 

golang.fyi/ch11/jsonserv0.go

Since we are leveraging HTTP as the transport protocol for the service, you can see the code is now much smaller than the prior implementation which used pure TCP. The currs function implements the handler responsible for incoming requests. It sets up a decoder to decode the incoming JSON-encoded request to a value of the curr1.CurrencyRequest type as highlighted in the following snippet:

var currRequest curr1.CurrencyRequest 
dec := json.NewDecoder(req.Body) 
if err := dec.Decode(&currRequest); err != nil { ... } 

Next, the function executes the currency search by calling curr1.Find(currencies, currRequest.Get) which returns the slice []Currency assigned to the result variable. The code then creates an encoder to encode the result as a JSON payload, highlighted in the following snippet:

result := curr1.Find(currencies, currRequest.Get) 
enc := json.NewEncoder(resp) 
if err := enc.Encode(&result); err != nil { ... } 

Lastly, the handler function is mapped to the "/currency" path in the main function with the call to mux.HandleFunc("/currency", currs). When the server receives a request for that path, it automatically executes the currs function.

Testing the API server with cURL

Because the server is implemented over HTTP, it can easily be tested with any client-side tools that support HTTP. For instance, the following shows how to use the cURL command line tool (http://curl.haxx.se/) to connect to the API end-point and retrieve currency information about the Euro:

$> curl -X POST -d '{"get":"Euro"}' http://localhost:4040/currency 
[ 
... 
  { 
    "currency_code": "EUR", 
    "currency_name": "Euro", 
    "currency_number": "978", 
    "currency_country": "BELGIUM" 
  }, 
  { 
    "currency_code": "EUR", 
    "currency_name": "Euro", 
    "currency_number": "978", 
    "currency_country": "FINLAND" 
  }, 
  { 
    "currency_code": "EUR", 
    "currency_name": "Euro", 
    "currency_number": "978", 
    "currency_country": "FRANCE" 
  }, 
... 
] 

The cURL command posts a JSON-formatted request object to the server using the -X POST -d '{"get":"Euro"}' parameters. The output (formatted for readability) from the server is comprised of a JSON array of the preceding currency items.

An API server client in Go

An HTTP client can also be built in Go to consume the service with minimal efforts. As is shown in the following code snippet, the client code uses the http.Client type to communicate with the server. It also uses the encoding/json sub-package to decode incoming data (note that the client also makes use of the curr1 package, shown earlier, which contains the types needed to communicate with the server):

import ( 
   "bytes" 
   "encoding/json" 
   "fmt" 
   "net/http" 
 
   " github.com/vladimirvivien/learning-go/ch11/curr1" 
) 
 
func main() { 
   var param string 
   fmt.Print("Currency> ") 
   _, err := fmt.Scanf("%s", &param) 
 
   buf := new(bytes.Buffer) 
   currRequest := &curr1.CurrencyRequest{Get: param} 
   err = json.NewEncoder(buf).Encode(currRequest) 
   if err != nil { 
         fmt.Println(err) 
         return 
   } 
 
   // send request 
   client := &http.Client{} 
   req, err := http.NewRequest( 
         "POST", "http://127.0.0.1:4040/currency", buf) 
   if err != nil { 
         fmt.Println(err) 
         return 
   } 
 
   resp, err := client.Do(req) 
   if err != nil { 
         fmt.Println(err) 
         return 
   } 
   defer resp.Body.Close() 
 
   // decode response 
   var currencies []curr1.Currency 
   err = json.NewDecoder(resp.Body).Decode(&currencies) 
   if err != nil { 
         fmt.Println(err) 
         return 
   } 
   fmt.Println(currencies) 
} 

golang.fyi/ch11/jsonclient0.go

In the previous code, an HTTP client is created to send JSON-encoded request values as currRequest := &curr1.CurrencyRequest{Get: param} where param is the currency string to retrieve. The response from the server is a payload that represents an array of JSON-encoded objects (see the JSON array in the section, Testing the API Server with cURL). The code then uses a JSON decoder, json.NewDecoder(resp.Body).Decode(&currencies), to decode the payload from the response body into the slice, []curr1.Currency.

A JavaScript API server client

So far, we have seen how to use the API service using the cURL command-line tool and a native Go client. This section shows the versatility of using HTTP to implement networked services by showcasing a web-based JavaScript client. In this approach, the client is a web-based GUI that uses modern HTML, CSS, and JavaScript to create an interface that interacts with the API server.

First, the server code is updated with an additional handler to serve the static HTML file that renders the GUI on the browser. This is illustrated in the following code:

// serves HTML gui 
func gui(resp http.ResponseWriter, req *http.Request) { 
   file, err := os.Open("./currency.html") 
   if err != nil { 
         resp.WriteHeader(http.StatusInternalServerError) 
         fmt.Println(err) 
         return 
   } 
   io.Copy(resp, file) 
} 
 
func main() { 
   mux := http.NewServeMux() 
   mux.HandleFunc("/", gui) 
   mux.HandleFunc("/currency", currs) 
 
   if err := http.ListenAndServe(":4040", mux); err != nil { 
         fmt.Println(err) 
   } 
} 

golang.fyi/ch11/jsonserv1.go

The preceding code snippet shows the declaration of the gui handler function responsible for serving a static HTML file that renders the GUI for the client. The root URL path is then mapped to the function with mux.HandleFunc("/", gui). So, in addition to the "/currency" path, which hosts the API end-point the "/" path will return the web page shown in the following screenshot:

A JavaScript API server client

The next HTML page (golang.fyi/ch11/currency.html) is responsible for displaying the result of a currency search. It uses JavaScritpt functions along with the jQuery.js library (not covered here) to post JSON-encoded requests to the backend Go service as shown in the following abbreviated HTML and JavaScript snippets:

<body> 
<div class="container"> 
  <h2>Global Currency Service</h2> 
  <p>Enter currency search string: <input id="in"> 
     <button type="button" class="btn btn-primary" onclick="doRequest()">Search</button> 
  </p>             
  <table id="tbl" class="table table-striped"> 
    <thead> 
      <tr> 
           <th>Code</th> 
           <th>Name</th> 
           <th>Number</th> 
           <th>Country</th> 
      </tr> 
    </thead> 
    <tbody/> 
  </table> 
</div> 
 
<script> 
 var tbl = document.getElementById("tbl"); 
   function addRow(code, name, number, country) { 
         var rowCount = tbl.rows.length; 
         var row = tbl.insertRow(rowCount); 
         row.insertCell(0).innerHTML = code; 
         row.insertCell(1).innerHTML = name; 
         row.insertCell(2).innerHTML = number; 
         row.insertCell(3).innerHTML = country; 
   } 
 
    function doRequest() { 
   param = document.getElementById("in").value 
        $.ajax('/currency', { 
            method: 'PUT', 
               contentType: 'application/json', 
               processData: false, 
               data: JSON.stringify({get:param}) 
         }).then( 
         function success(currencies) { 
               currs = JSON.parse(currencies) 
               for (i=0; i < currs.length; i++) { 
                     addRow( 
                           currs[i].currency_code, 
                           currs[i].currency_name, 
                           currs[i].currency_number, 
                           currs[i].currency_country 
                     ); 
               } 
 
         }); 
   } 
</script> 

golang.fyi/ch11/currency.html

A line-by-line analysis of the HTML and JavaScript code in this example is beyond the scope of the book; however, it is worth pointing out that the JavaScript doRequest function is where the interaction between the client and the server happens. It uses the jQuery's $.ajax function to build an HTTP request with a PUT method and to specify a JSON-encoded currency request object, JSON.stringify({get:param}), to send to the server. The then method accepts the callback function, success(currencies), which handles the response from the server that parses displays in an HTML table.

When a search value is provided in the text box on the GUI, the page displays its results in the table dynamically as shown in the following screenshot:

A JavaScript API server client
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.209.131