Часто API защищены уникальным APIKEY для каждого приложения. Требование этих API заключается в том, что вы отправляете свой APIKEY с каждым запросом либо через строку запроса, либо в пользовательском заголовке. Но когда ваше приложение является одностраничным приложением (SPA), вы не можете встроить свой APIKEY в свое приложение, потому что это небезопасный способ обработки конфиденциальных данных, а поддержка CORS не включена.
Чтобы решить эту проблему обработки защищенных данных при написании потребителя SPA, вы должны использовать прокси-приложение. Прокси-сервер примет все запросы от SPA, защитит их с помощью CORS, добавит APIKEY и перенаправит запрос в защищенный API. И он перенаправит ответ от API обратно в запрашивающее SPA.
Мы будем делать вызов API к Yahoo! Финансовый API на RapidAPI. Этот API использует APIKEY и является хорошим примером для этого руководства. Используемый здесь метод может быть применен к любому API, для которого требуется APIKEY.
В рамках Спецификации HTTP API могут выполнять запросы, в том числе с использованием следующих HTTP-методов:
Прокси-приложение может обрабатывать любой или все из этих пяти HTTP-команд. В этой статье описывается, как создать прокси-приложение GET для использования API.
Прокси-контроллер принимает один параметр с кодировкой urlencoded, url
, в одной конечной точке RPC в следующем формате:
https://proxy.server/proxy?url=%2Fmarket%2Fget-summary%2F1
Затем ваш ProxyAPI RPC может обработать URL-адрес и добавить APIKEY к запросу, а затем перенаправить его в API данных. Это позволяет вам писать запросы, как если бы они были непосредственно в API.
Пример прокси-кода
Это действие контроллера, написанное на PHP в Zend Framework, реализует прокси для API для запросов GET. Пожалуйста, смотрите комментарии по всему коду.
use Exception; use Zend\Mvc\Controller\AbstractActionController; use Zend\Uri\UriFactory; use Zend\Http\Client; use Zend\Http\Request; use Zend\Http\Response; use ZF\ApiProblem\ApiProblem; use ZF\ApiProblem\ApiProblemResponse; class ProxyController extends AbstractActionController { private $config = [ 'x-rapidapi-host' => 'apidojo-yahoo-finance-v1.p.rapidapi.com', 'x-rapidapi-key' => "12345678901234567890123456789012345678901234567890", 'region' => 'US', 'language' => 'en', ]; public function proxyAction() { $url = $this->params()->fromQuery('url'); // Extract the URI, append region and language to request $uri = UriFactory::factory('https://' . $this->config['x-rapidapi-host'] . '/' . $url); $query = $uri->getQueryAsArray(); $query['region'] = $this->config['region']; $query['lang'] = $this->config['language']; $uri->setQuery($query); // Run proxy based on the request method which with this was called. switch ($this->getRequest()->getMethod()) { case 'GET': // Here would be a good spot to add caching // Handle GET query requests with no body $client = new Client((string) $uri); $client->setMethod($this->getRequest()->getMethod()); // Copy all headers from request to this server into the // request for the API call. Append x-rapidapi* headers $client->getRequest() ->getHeaders() ->addHeaders($this->getRequest()->getHeaders()) ->addHeaderLine('Accept', 'application/json') ->addHeaderLine('x-rapidapi-key', $this->config['x-rapidapi-key']) ->addHeaderLine('x-rapidapi-host', $this->config['x-rapidapi-host']) ; try { // Try making the API call $response = $client->send(); if ($response->getStatusCode() !== 200) { // Use API Problem to return a non-200 status code which did not // trigger an exception. $apiProblem = new ApiProblem( $response->getStatusCode(), $response->getBody() ); $apiProblemResponse = new ApiProblemResponse($apiProblem); return $apiProblemResponse; } } catch (Exception $e) { // Handle all exceptions with ApiProblem $apiProblem = new ApiProblem(500, $e->getMessage() . ' ' . (string) $uri); $apiProblemResponse = new ApiProblemResponse($apiProblem); return $apiProblemResponse; } // Return the response from the api directly to the requsting // API consumer return $response; case 'POST': case 'PATCH': case 'PUT': case 'DELETE': default: return $this->getResponse(); } } }
| CORS — это метод, позволяющий совместно использовать ресурсы между сценариями, работающими в клиенте браузера, и ресурсами из другого источника.
CORS используется для обмена данными между браузером и API. CORS не используется при обмене данными между сервером и API. Поскольку SPA полностью запускаются в браузере, для доступа к API-интерфейсу прокси-сервера используются соответствующие заголовки CORS. Существует множество библиотек для реализации CORS. Я использую модуль https://github.com/zf-fr/zfr-cors. Вам нужно будет внедрить CORS, иначе вы увидите ошибку, аналогичную Нет заголовка Access-Control-Allow-Origin в запрошенном ресурсе.
Звонки на прокси из SPA
Вот фрагмент TypeScript, который использует прокси для вызова API к Yahoo! API на RapidAPI:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from '@env'; /** * For brevity the MarketSymbol class does not include all fields from * an API response. */ class MarketSymbol { symbol: string; fullExchangeName: string; market: string; regularMarketPrice: { raw: number; fmt: number; }; } /** * This is the structure of the response expected from the * MarketSummary API call. */ class MarketSummary { marketSummaryResponse: { result: Array<MarketSymbol>; error: any; }; } @Injectable({ providedIn: 'root' }) export class YahooFinanceProxyService { /** * The configuration looks like */ // export const environment = { // production: false, // apiUrl: 'https://proxy.server/proxy?url=' // } private apiUrl = environment.apiUrl; constructor( private http: HttpClient ) { } public getMarketSummary(): Observable<MarketSummary> { { return this.http.get<MarketSummary>(this.apiUrl + encodeUriComponent('/market/get-summary')); } }
Другие подходы к прокси
Apache HTTP и nginx включают прокси-мод. Должна быть возможность создать конфигурацию, чтобы ко всему входящему трафику APIKEY добавлялся к запросу, который затем перенаправлялся на сервер API. CORS также необходимо обрабатывать с помощью конфигурации прокси. Это выходит за рамки данной статьи.
Вывод
По мере того, как SPA созрели, доступ к API с их помощью станет обычным явлением. Но до тех пор, пока больше разработчиков не реализуют OAuth2 вместо APIKEY, прокси-серверы для API-интерфейсов будут средством экономии времени, позволяющим избежать кодирования API-интерфейса в качестве серверного приложения, которое затем будет использоваться в качестве API-интерфейса для SPA.
Первоначально опубликовано на https://rapidapi.com 16 января 2020 г.