
Часто 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 г.