Одна из наиболее распространенных задач, которые мы выполняем с серверным приложением, - это создание простого прокси API. Под этим я подразумеваю, что мы предоставляем API на нашем сервере, который просто выполняет прокси-сервер на другой удаленный сервер. Зачем вам это нужно, если вы можете легко вызывать API-интерфейсы на стороне клиента с помощью JavaScript?

  • Для удаленного API может потребоваться ключ. Включение вашего ключа в код JavaScript означает, что он доступен для всех и может использоваться другими, что может заблокировать вам доступ к API или увеличить ваши расходы.
  • Удаленный API может возвращать данные в неразборчивом формате, например XML.
  • Удаленный API может возвращать данные, которые вам не нужны, что приводит к более медленному отклику, включающему данные, которые вы никогда не будете использовать.
  • Удаленный API может работать с данными, которые не нуждаются в частом обновлении. Затем ваш сервер может добавить свой собственный слой кеширования.
  • Удаленный API может выйти из строя. Имея собственную точку входа, вы можете справиться с этим несколькими способами, используя либо статические данные, либо какой-либо другой результат, который не сломает клиентов полностью.
  • Наконец, удаленный API может быть куплен какой-нибудь крупной компанией (возможно, рифмуемой с MaceLook) и отключен. Вы можете полностью заменить удаленный API, и ваши клиенты никогда не узнают.

По всем этим и многим другим причинам может иметь смысл создать простой сервер для ваших веб-приложений и направить ваш код на прокси-сервер API, а не непосредственно на удаленный сервер. Хотя с Node.js это было бы довольно тривиально, я подумал, что это может быть отличным вариантом использования и для бессерверной версии, особенно когда я только что обнаружил, насколько хорош OpenWhisk. (См. Мой прошлогодний пост: Переход без сервера с OpenWhisk)

Для своего API я решил использовать Cat API. Cat API возвращает случайные изображения кошек. Он позволяет фильтровать по категориям, голосовать за кошек и многое другое. Хотя для этого не требуется ключ API, но если вы его не передадите, вы ограничитесь набором из 1000 кошек, что, как мы все знаем, слишком мало. Кроме того, он возвращает XML. Это мерзко, но я все равно прощу их, раз уж мы все еще говорим о кошках.

Я предполагаю, что вы читали мой предыдущий пост, но на всякий случай, вот краткий обзор того, как работает OpenWhisk. Вы загружаете программу командной строки (технически это необязательно), пишете свое действие (программа, которая выполняет одно действие), а затем развертываете ее в OpenWhisk. Последний шаг - предоставить действие через REST API. Красиво и просто, правда?

Для своего тестирования я решил построить два действия:

  • Первое действие вернет список из 10 случайных кошек. Он вернет URL-адреса изображений в небольшом формате для списка.
  • Второе действие вернет сведения об одном изображении и запросит URL-адрес большего изображения.

Начнем с первого действия. Я начал с простого определения URL-адреса:

Http://thecatapi.com/api/images/get?format=xml&results_per_page=10&size=small&api_key=SECRET

Теперь я начал работать над своим действием. Одна из первых проблем, с которой я столкнулся, заключалась в том, как я обрабатываю XML. Хотя я мог попытаться разобрать его вручную, я действительно хотел использовать пакет npm, но я также знал, что OpenWhisk поддерживает только определенный список пакетов npm. Я проверил тот список и был рад увидеть, что xml2js был включен! Вот действие, которое я написал:

var request = require('request');
var parseString = require('xml2js').parseString;

function main() {

    var getCatsURL = 'http://thecatapi.com/api/images/get?format=xml&results_per_page=10&size=small&api_key=visitmywishlist';

    return new Promise((resolve, reject) => {

        request.get(getCatsURL, function(error, response, body) {

            if(error) {
                reject(error);
            } else {

                //lets fix the response
                parseString(body, function(err, result) {
                    if(err) {
                        reject(err);
                    } else {
                        let myResult = result.response.data[0].images[0].image;
                        /*
                        At this point, we have a nice array of results, but each result
                        needs a small change. They look like this:

                         {"id": ["d8p" ],
                         "source_url":["http://thecatapi.com/?id=d8p"],
                         "url":["http://25.media.tumblr.com/tumblr_m4g7z0X8fq1qhwmnpo1_250.jpg"]},                                                                       Notice how the key values are 1-length arrays   
                        
                        */
                        let newResult = myResult.map((cat) => {
                            return {
                                id:cat.id[0],
                                source_url:cat.source_url[0],
                                url:cat.url[0]
                            };
                        });
                        resolve({response:newResult});
                    }
                });
            }

        });


    });

}

Давайте разберемся с этим по крупицам. Я начинаю с простого запроса URL-адреса, который возвращает мой список кошек. Затем я передаю его методу parseString пакета xml2js. Это преобразует XML в простой объект, но делает это довольно подробным образом. Чтобы понять, что мне нужно, я сначала просто вернул result самого синтаксического анализа. Я посмотрел на результат в CLI и медленно сократил его до самого необходимого, что мне было нужно. Напоминаем, что вы можете очень легко вызывать действия из интерфейса командной строки:

wsk action invoke catlist --blocking

Я также немного помассирую результаты, так как он возвращал пары ключ / значение в виде массивов длины 1. Вот пример того, что я теперь получаю:

Чтобы было ясно, вы видите полный результат действия (ну, насколько я мог вместить на снимке экрана), который включает метаданные о вызове действия и самом результате. Что будут использовать наши клиенты, находится в разделе response.result.

Следующее действие обрабатывает получение сведений для конкретного идентификатора. Вот это действие:

var request = require('request');
var parseString = require('xml2js').parseString;
function main(params) {
    var getCatsURL = 'http://thecatapi.com/api/images/get?format=xml&image_id='+params.id+'&size=large&api_key=mymilkshake';
    return new Promise((resolve, reject) => {
        request.get(getCatsURL, function(error, response, body) {
            if(error) {
                reject(error);
            } else {
                //lets fix the response
                parseString(body, function(err, result) {
                    if(err) {
                        reject(err);
                    } else {
                        let myResult = result.response.data[0].images[0].image[0];
                        let detail = {url:myResult.url[0], source_url:myResult.source_url[0], id:myResult.id[0]};
                        resolve({response:detail});
                    }
                });
            }
        });

    });
}

Это еще проще, так как нам просто нужно массировать результат одного элемента. Обратите внимание, как я получаю доступ к ожидаемому параметру для идентификатора: params.id. И, честно говоря, это все. Одна проблема, которую вы можете увидеть, заключается в том, что у меня есть один ключ API в двух отдельных файлах. Я мог бы исправить это, переключив свой код в пакет (см. Документацию), но для этой конкретной демонстрации это было излишним. Это определенно был бы правильный путь, если бы я начал добавлять больше действий, связанных с этим конкретным API.

Последним шагом было раскрыть это через REST API. Напоминаем, что эта работа все еще продолжается, поэтому вы, вероятно, пока не хотите делать это для настоящего кода, но он уже отлично работает. В общем процессе с помощью интерфейса командной строки вы определяете корневой путь API, конкретный путь для this вызова, метод HTTP и, наконец, вызываемое действие.

У меня есть два действия и, следовательно, два вызова API. Я использовал корневой путь /cats и определил /list и /detail для каждой части моего API. Я показал CLI для этого в моем последнем посте, но вот он снова:

wsk api-experimental create /cats /list get catwrapper

Если вы забыли, какие API-интерфейсы вы определили, вы также можете увидеть их через интерфейс командной строки:

wsk api-experimental list

Как только я определил свои API, технически я был готов. Я создал приложение Ionic. Это просто тип «мастер / деталь», но вот он в действии. Во-первых, список:

И деталь - эта кнопка загружает страницу деталей на сайте Cat API.

Полный код этого приложения можно найти здесь (https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionic_openwhisk), но давайте посмотрим на поставщика:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
/*
  Generated class for the CatProvider provider.
  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular 2 DI.
*/
@Injectable()
export class CatProvider {
  private LIST_URL = 'https://3b1fd5b1-e8cc-4871-a7d8-cc599e3ef852-gws.api-gw.mybluemix.net/cats/list';
  private DETAIL_URL = 'https://3b1fd5b1-e8cc-4871-a7d8-cc599e3ef852-gws.api-gw.mybluemix.net/cats/detail';
  constructor(public http: Http) {
    console.log('Hello CatProvider Provider');
  }
  list() {
    return this.http.get(this.LIST_URL)
    .map(res => res.json())
    .map(data => data.response);
  }
  get(id) {
    return this.http.get(this.DETAIL_URL + '?id='+id)
    .map(res => res.json())
    .map(data => data.response);
  }
}

Так что да, здесь нет ничего особенного, ничего необычного, что именно то, что мы хотим! Наше приложение Ionic взаимодействует с нашей реализацией OpenWhisk, и фактический API полностью скрыт от пользователя. (Ладно, технически нет - поскольку на сайте открывается страница с подробностями, но суть вы поняли.) Ключ API отсутствует. Нет синтаксического анализа XML. Все это скрыто за кадром. И что еще лучше, мне не пришлось писать или настраивать собственный сервер NodeJS. Все, что я сделал, это написал два небольших действия и развернул их, и я был готов.

Первоначально опубликовано на странице https://www.raymondcamden.com/2017/01/02/building-a-serverless-api-proxy-with-openwhisk