Warning: Undefined array key "typ" in /srv/vhost/diaridigital.net/home/html/sourcecode/main/articles.php on line 18
Consumir un webservice REST
Tiempo de lectura: 1 minutos
Conectar a una API de tipo REST utilizando OAuth 1.0
FatSecret API
FatSecret es una base de datos nutricional en la que se puede buscar un alimento para saber su composición nutricional, entro otras muchas cosas. Es necesario disponer de una API Key para poder ejecutar la llamada y que devuelva valores.
Necesitaremos los siguientes imports en el programa principal:
import (
"encoding/xml"
"fmt"
"io"
"log"
"net/http"
"strings"
// local
"webServices/internal/config"
"webServices/pkg/fatsecret"
) Pedir el alimento
Empezamos creando una lectura de consola para que el usuario nos indique el alimento a buscar y guardamos la variable global.
func getUserData() {
fmt.Print("Alimento: ")
fmt.Scan(&food)
if food == "" {
return
}
} Montamos un función que realice la conexión a la API, que en realidad simplemente almacena las claves y crea una clase para acceder al cliente. Con la clase fs tenemos acceso a los métodos del cliente.
func callFatsecret(fsApiKey map[string]string) {
fs, err := fatsecret.Connect(fsApiKey["apikey"], fsApiKey["secret"])
if err != nil {
panic(err)
}
foods, err := fs.FoodSearch(food)
if err != nil {
panic(err)
}
fmt.Println(foods[0])
} Aunque en Go no hay clases, nos devuelve fs que podemos considerar una clase con los métodos fs.FoodSearch y fs.get. Y cuya estructura de datos y métodos es
type FatSecretConn struct {
apikey string
secret string
}
func (fs FatSecretConn) FoodSearch
func (fs FatSecretConn) get FoodSearch
fs.FoodSearch procesa el resultado obtenido al hacer la llamada a la API en fs.get.
func (fs FatSecretConn) FoodSearch(query string) ([]Food, error) {
// llamada a la PI
resp, err := fs.get(
"foods.search",
map[string]string{"search_expression": query},
)
if err != nil {
return nil, err
}
// lectura de la respuesta en JSON
body, err := io.ReadAll(resp)
if err != nil {
return nil, err
}
defer resp.Close()
// procesamos la parte de JSON que nos interesa
foodresp := FoodSearchResponse{}
err = json.Unmarshal(body, &foodresp)
if err != nil {
return nil, err
}
if foodresp.Error != nil {
return nil, errors.New(foodresp.Error.Message)
}
// retornamos el array
return foodresp.Foods.Food, nil
} get
El método get básicamente monta los parámetros necesarios para hacer el request y devolver el body. Para utilizar OAuth tenemos que crear una firma de los parámetros del request.
func (fs FatSecretConn) get(method string, params map[string]string) (io.ReadCloser, error) {
reqTime := fmt.Sprintf("%d", time.Now().Unix())
r := rand.New(rand.NewSource(time.Now().UnixNano()))
m := map[string]string{
"method": method,
"oauth_consumer_key": fs.apikey,
"oauth_nonce": fmt.Sprintf("%d", r.Int63()),
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": reqTime,
"oauth_version": "1.0",
"format": "json",
}
// las claves deben estar ordenadas alfabéticamente
for k, v := range params {
m[k] = v
}
mk := make([]string, len(m))
i := 0
for k := range m {
mk[i] = k
i++
}
sort.Strings(mk)
// monta las clase/valor ordenadas para la firma
sigQueryStr := ""
for _, k := range mk {
sigQueryStr += fmt.Sprintf("&%s=%s", k, escape(m[k]))
}
// elimina el & inicial
sigQueryStr = sigQueryStr[1:]
// monta el string base para firmarlo
sigBaseStr := fmt.Sprintf("GET&%s&%s", url.QueryEscape(fsurl), escape(sigQueryStr))
// crea una firma
mac := hmac.New(sha1.New, []byte(fs.secret+"&"))
mac.Write([]byte(sigBaseStr))
sig := base64.StdEncoding.EncodeToString(mac.Sum(nil))
// añade la firma al mapa de parámetros
m["oauth_signature"] = sig
mk = append(mk, "oauth_signature")
// reordena las claves
sort.Strings(mk)
requrl := fmt.Sprintf("%s?", fsurl)
reqQuery := ""
for _, k := range mk {
reqQuery += fmt.Sprintf("&%s=%s", k, escape(m[k]))
}
// elimina el & inicial
reqQuery = reqQuery[1:]
// envia el request
requrl += reqQuery
resp, err := http.Get(requrl)
if err != nil {
return nil, err
}
return resp.Body, nil
} Respuesta
Esta es la respuesta obtenida después de procesar el JSON
{2057 Salmon Generic Per 100g - Calories: 146kcal | Fat: 5.93g | Carbs: 0.00g | Protein: 21.62g}
{2058 Cooked Salmon Per 100g - Calories: 139kcal | Fat: 4.30g | Carbs: 0.00g | Protein: 23.45g}
{2059 Baked Salmon Per 845g - Calories: 1445kcal | Fat: 63.87g | Carbs: 4.14g | Protein: 202.52g} El código completo del cliente original se puede encontrar en GitHub.
- Código
https://github.com