Esta é a documentação do Apigee Edge.
Acesse
Documentação da Apigee X. informações
Edge Microgateway v. 3.0.x
Público-alvo
Este tópico é destinado aos desenvolvedores que querem ampliar os recursos do Edge Microgateway escrevendo e plug-ins personalizados. Se você quer escrever um novo plug-in, é necessário ter experiência com JavaScript e Node.js obrigatórios.
O que é um plug-in personalizado do Edge Microgateway?
Um plug-in é um módulo Node.js que adiciona funcionalidade ao Edge Microgateway. Módulos de plug-in seguem um padrão consistente e são armazenados em um local conhecido pelo Edge Microgateway, descoberta e execução automática deles. Vários plug-ins predefinidos são fornecidos quando você instalar o Edge Microgateway. Isso inclui plug-ins para autenticação, controle de pico, cotas e análise de dados em nuvem. Esses plug-ins estão descritos em Usar plug-ins.
É possível adicionar novos recursos ao microgateway escrevendo recursos personalizados plug-ins. Por padrão, o Edge Microgateway é basicamente um proxy de passagem segura que transmite solicitações e respostas inalteradas de e para serviços de destino. Com plug-ins personalizados, é possível interagir programaticamente com as solicitações e respostas que fluem pelo microgateway.
Onde colocar o código do plug-in personalizado
Uma pasta para plug-ins personalizados está incluída como parte da instalação do Edge Microgateway aqui:
[prefix]/lib/node_modules/edgemicro/node_modules/microgateway-plugins
em que [prefix]
é o diretório do prefixo npm
conforme
descritos em "Onde o Edge Microgateway está instalado" em Como instalar o Edge
Microgateway
É possível alterar esse diretório de plug-in padrão. Consulte Onde encontrar plug-ins.
Como revisar os plug-ins predefinidos
Antes de tentar desenvolver seu próprio plug-in, é bom verificar se nenhum dos plug-ins plug-ins atendam às suas necessidades. Esses plug-ins estão localizados em:
[prefix]/lib/node_modules/edgemicro/node_modules/microgateway-plugins
em que [prefix]
é o diretório do prefixo npm
. Consulte
"Onde o Edge Microgateway está instalado" em Como instalar o Edge
Microgateway
Para mais detalhes, consulte também Predefinidos plug-ins fornecidos com o Edge Microgateway.
Criar um plug-in simples
Nesta seção, você verá as etapas necessárias para criar um plug-in simples. Este plug-in substitui os dados de resposta (seja qual for) pela string "Hello, World!" e o imprime no terminal.
- Se o Edge Microgateway estiver em execução, interrompa-o agora:
edgemicro stop
-
cd
ao diretório do plug-in personalizado:cd [prefix]/lib/node_modules/edgemicro/plugins
em que
[prefix]
é o diretório do prefixonpm
. conforme descrito em "Onde o Edge Microgateway está instalado" em Como instalar o Edge Microgateway - Crie um novo projeto de plug-in chamado response-override e
cd
. a ela:
mkdir response-override && cd response-override
- Crie um novo projeto Node.js:
Clique em "Voltar" várias vezes para aceitar os valores padrão.npm init
- Use um editor de texto para criar um novo arquivo com o nome
index.js
. - Copie o código abaixo para
index.js
e salve o arquivo.
'use strict'; var debug = require('debug') module.exports.init = function(config, logger, stats) { return { ondata_response: function(req, res, data, next) { debug('***** plugin ondata_response'); next(null, null); }, onend_response: function(req, res, data, next) { debug('***** plugin onend_response'); next(null, "Hello, World!\n\n"); } }; }
- Você criou um plug-in e precisa adicioná-lo à configuração do Edge Microgateway.
Abra o arquivo
$HOME/.edgemicro/[org]-[env]-config.yaml
, em queorg
eenv
são os nomes da organização de borda e dos ambientes; - Adicione o plug-in
response-override
ao elementoplugins:sequence
. conforme mostrado abaixo.
... plugins: dir: ../plugins sequence: - oauth - response-override ...
- Reinicie o Edge Microgateway.
- Chamar uma API pelo Edge Microgateway. Essa chamada de API pressupõe que você tenha configurado a mesma
do projeto como o tutorial com a segurança da chave de API, conforme descrito em Como configurar
e configurar o Edge Microgateway:
curl -H 'x-api-key: uAM4gBSb6YoMvTHfx5lXJizYIpr5Jd' http://localhost:8000/hello/echo Hello, World!
Anatomia de um plug-in
O plug-in de amostra do Edge Microgateway a seguir ilustra o padrão a ser seguido ao
desenvolver plug-ins próprios. O código-fonte do plug-in de exemplo discutido nesta seção é
em plugins/header-uppercase/index.js.
- Os plug-ins são módulos NPM padrão com
package.json
eindex.js
. na pasta raiz. - Um plug-in precisa exportar uma função init().
- A função init() recebe três argumentos: config, logger e stats. Esses argumentos são descritos no plug-in init().
- init() retorna um objeto com gerenciadores de funções nomeados que são chamados quando certos eventos ocorrem durante o ciclo de vida de uma solicitação.
Funções do manipulador de eventos
Um plug-in precisa implementar algumas ou todas essas funções de manipulador de eventos. A implementação dessas funções é você quem decide. Toda função é opcional, e um plug-in típico implementará em pelo menos um subconjunto dessas funções.
Gerenciadores de eventos de fluxo de solicitação
Essas funções são chamadas em eventos de solicitação no Edge Microgateway.
onrequest
ondata_request
onend_request
onclose_request
onerror_request
Função onrequest
Chamado no início da solicitação do cliente. Essa função é acionada quando o primeiro byte do solicitação é recebida pelo Edge Microgateway. Essa função dá acesso aos cabeçalhos das solicitações, URL, parâmetros de consulta e método HTTP. Se você chamar em seguida com um primeiro argumento verdadeiro (como um instância de Erro), o processamento da solicitação será interrompido e uma solicitação de destino não será iniciada.
Exemplo:
onrequest: function(req, res, next) { debug('plugin onrequest'); req.headers['x-foo-request-start'] = Date.now(); next(); }
Função ondata_request
Chamado quando uma parte de dados é recebida do cliente. Transmite dados da solicitação para o próximo na sequência do plug-in. O valor retornado do último plug-in na sequência é enviado para ao alvo. Um caso de uso típico, mostrado abaixo, é transformar os dados da solicitação antes de enviá-los ao destino.
Exemplo:
ondata_request: function(req, res, data, next) { debug('plugin ondata_request ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }
Função onend_request
Chamado quando todos os dados da solicitação foram recebidos do cliente.
Exemplo:
onend_request: function(req, res, data, next) { debug('plugin onend_request'); next(null, data); }
onclose_request
função
Indica que a conexão com o cliente foi encerrada. Você pode usar essa função nos casos em que não é confiável. Ele é chamado quando a conexão de soquete com o cliente é fechadas.
Exemplo:
onclose_request: function(req, res, next) { debug('plugin onclose_request'); next(); }
onerror_request
função
Chamado se houver um erro ao receber a solicitação do cliente.
Exemplo:
onerror_request: function(req, res, err, next) { debug('plugin onerror_request ' + err); next(); }
Manipuladores de eventos de fluxo de resposta
Essas funções são chamadas em eventos de resposta no Edge Microgateway.
onresponse
ondata_response
onend_response
onclose_response
onerror_response
onresponse
função
Chamado no início da resposta de destino. Essa função é acionada quando o primeiro byte do ela for recebida pelo Edge Microgateway. Esta função fornece acesso aos cabeçalhos de resposta e o código de status.
Exemplo:
onresponse: function(req, res, next) { debug('plugin onresponse'); res.setHeader('x-foo-response-time', Date.now() - req.headers['x-foo-request-start']) next(); }
Função ondata_response
Chamado quando um bloco de dados é recebido do destino.
Exemplo:
ondata_response: function(req, res, data, next) { debug('plugin ondata_response ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }
Função onend_response
Chamado quando todos os dados de resposta foram recebidos do destino.
Exemplo:
onend_response: function(req, res, data, next) { debug('plugin onend_response'); next(null, data); }
onclose_response
função
Indica que a conexão de destino foi encerrada. Você pode usar essa função nos casos em que de destino não é confiável. Ele é chamado quando a conexão de soquete com o destino fechadas.
Exemplo:
onclose_response: function(req, res, next) { debug('plugin onclose_response'); next(); }
Função onerror_response
Chamado se houver um erro ao receber a resposta de destino.
Exemplo:
onerror_response: function(req, res, err, next) { debug('plugin onerror_response ' + err); next(); }
O que você precisa saber sobre funções do manipulador de eventos do plug-in
As funções de manipulador de eventos de plug-in são chamadas em resposta a eventos específicos que ocorrem enquanto O Edge Microgateway processa uma determinada solicitação de API.
- Cada um dos gerenciadores de função init() (ondata_request, ondata_response etc.) precisa chamar o callback next() quando terminar. processamento. Se você não chamar next(), o processamento será interrompido, e a solicitação travará.
- O primeiro argumento para next() pode ser um erro que fará com que a solicitação o processamento seja encerrado.
- Os gerenciadores ondata_ e onend_ precisam chamar next() por um segundo argumento contendo os dados a serem transmitidos para o destino ou o cliente. Este argumento pode ser nulo se o plug-in estiver armazenando em buffer e não tiver dados suficientes para se transformam no momento.
- Observe que uma única instância do plug-in é usada para atender a todas as solicitações e respostas. Se um plug-in quiser reter o estado por solicitação entre chamadas, ele pode salvar esse estado em um adicionada ao objeto request fornecido (req), cujo o ciclo de vida é a duração da chamada de API.
- Tenha cuidado para capturar todos os erros e chame next() com o erro. Falha em chamada next() resultará na suspensão da chamada da API.
- Tenha cuidado para não introduzir vazamentos de memória, porque isso pode afetar o desempenho geral do Edge Microgateway e faz com que ele falhe se ficar sem memória.
- Siga o modelo Node.js e não realize tarefas que exigem muita computação na já que isso pode afetar negativamente o desempenho do Edge Microgateway.
Sobre a função init() do plug-in
Esta seção descreve os argumentos passados para a função init(): config, logger e stats.
config
Um objeto de configuração gerado após a mesclagem do arquivo de configuração do Edge Microgateway com o
informações transferidas por download do Apigee Edge, como produtos e cotas. Você pode encontrar
configuração específica do plug-in neste objeto: config.<plugin-name>
.
Para adicionar um parâmetro de configuração chamado param com um valor foo
a um plug-in chamado response-override, coloque-o no campo default.yaml
arquivo:
response-override: param: foo
Em seguida, você pode acessar o parâmetro no código do plug-in da seguinte forma:
// Called when response data is received ondata_response: function(req, res, data, next) { debug('***** plugin ondata_response'); debug('***** plugin ondata_response: config.param: ' + config.param); next(null, data); },
Nesse caso, você verá foo impresso na saída de depuração do plug-in:
Sun, 13 Dec 2015 21:25:08 GMT plugin:response-override ***** plugin ondata_response: config.param: foo
logger
O registrador do sistema. O logger empregado atualmente exporta essas funções, onde o objeto pode ser uma string, solicitação HTTP, resposta HTTP ou instância de erro.
info(object, message)
warn(object, message)
error(object, message)
stats
Objeto que contém contagens de solicitações, respostas, erros e outras estatísticas agregadas. relacionadas às solicitações e respostas que passam por uma instância de microgateway.
- treqErrors: o número de solicitações de destino com erros.
- treqErrors: o número de respostas de destino com erros.
- statusCodes: um objeto que contém contagens de código de resposta:
{ 1: number of target responses with 1xx response codes 2: number of target responses with 2xx response codes 3: number of target responses with 3xx response codes 4: number of target responses with 4xx response codes 5: number of target responses with 5xx response codes }
- requests: o número total de solicitações.
- responses: o número total de respostas.
- connections: O número de conexões de destino ativas.
Sobre a função next()
Todos os métodos do plug-in precisam chamar next()
para continuar processando o próximo método na
(ou o processo do plug-in travará). No ciclo de vida da solicitação, o primeiro método chamado é
onrequest(). O próximo método a ser chamado é ondata_request()
. No entanto,
O ondata_request
só é chamado se a solicitação incluir dados, como em
o caso, por exemplo, de uma solicitação POST. O próximo método chamado será
onend_request()
, que é chamado quando o processamento da solicitação é concluído. A
As funções onerror_*
são chamadas apenas em caso de erro e permitem que você
e lidar com os erros com código personalizado, se quiser.
Digamos que os dados sejam enviados na solicitação e o método ondata_request()
seja chamado. Aviso
que a função chame next()
com dois parâmetros:
next(null, data);
Por convenção, o primeiro parâmetro é usado para transmitir informações de erro, que você pode então
em uma função subsequente na cadeia. Ao defini-lo como null
, um false
, significa que não há erros e que o processamento da solicitação deve continuar normalmente. Se
esse argumento for verdadeiro (como um objeto Error), o processamento da solicitação será interrompido e a solicitação será
enviados ao destino.
O segundo parâmetro passa os dados da solicitação para a próxima função na cadeia. Se você não
processamento adicional, os dados da solicitação serão transmitidos sem alterações para o destino da API.
No entanto, você tem a chance de modificar os dados da solicitação nesse método e transmitir o valor modificado
para o destino. Por exemplo, se os dados da solicitação forem XML e o destino esperar JSON,
é possível adicionar um código ao método ondata_request()
que (a) mude a
Content-Type do cabeçalho da solicitação para application/json
e converte os dados da solicitação
JSON usando o que você quiser (por exemplo, você pode usar uma biblioteca Node.js
xml2json recebido pelo NPM.
Vamos conferir como isso vai ficar:
ondata_request: function(req, res, data, next) { debug('****** plugin ondata_request'); var translated_data = parser.toJson(data); next(null, translated_data); },
Nesse caso, os dados da solicitação (que são considerados XML) são convertidos em JSON, e a
os dados transformados são transmitidos por next()
para a próxima função na cadeia de solicitações.
antes de serem passados para o destino do back-end.
É possível adicionar outra instrução de depuração para imprimir os dados transformados para depuração. propósitos. Exemplo:
ondata_request: function(req, res, data, next) { debug('****** plugin ondata_request'); var translated_data = parser.toJson(data); debug('****** plugin ondata_response: translated_json: ' + translated_json); next(null, translated_data); },
Sobre ordem de execução do gerenciador de plug-ins
Se você criar plug-ins para o Edge Microgateway, precisará entender a ordem em que o plug-in manipuladores de eventos são executados.
É importante lembrar que, quando você especifica uma sequência de plug-ins no Arquivo de configuração de microgateway, os gerenciadores de solicitação são executados em ordem crescente, enquanto os gerenciadores de resposta são executados em ordem decrescente.
O exemplo a seguir foi criado para ajudar você a entender essa sequência de execução.
1. Crie três modelos plug-ins
Considere o plug-in a seguir. Tudo o que ela faz é imprimir a saída do console quando seus manipuladores de eventos são chamado:
plugins/plugin-1/index.js (em inglês)
module.exports.init = function(config, logger, stats) { return { onrequest: function(req, res, next) { console.log('plugin-1: onrequest'); next(); }, onend_request: function(req, res, data, next) { console.log('plugin-1: onend_request'); next(null, data); }, ondata_response: function(req, res, data, next) { console.log('plugin-1: ondata_response ' + data.length); next(null, data); }, onend_response: function(req, res, data, next) { console.log('plugin-1: onend_response'); next(null, data); } }; }
Agora, considere criar mais dois plug-ins, plugin-2
e plugin-3
, com
o mesmo código (exceto, mude as instruções console.log()
para plugin-2
)
e plugin-3
, respectivamente).
2. Revisar o código do plug-in
As funções do plug-in exportado
<microgateway-root-dir>/plugins/plugin-1/index.js
são manipuladores de eventos que
são executados em momentos específicos durante o processamento de solicitações e respostas. Por exemplo:
onrequest
executa o primeiro byte dos cabeçalhos da solicitação recebido. Enquanto,
onend_response
é executado depois que o último byte de dados de resposta é recebido.
Observe o manipulador ondata_response - ele é chamado sempre que um bloco de dados de resposta sejam recebidos. O importante a saber é que os dados de resposta não são necessariamente recebidos uma vez. Em vez disso, os dados podem ser recebidos em blocos de tamanho arbitrário.
3. Adicionar os plug-ins ao a sequência do plug-in
Continuando com este exemplo, vamos adicionar os plug-ins à sequência de plug-ins no Edge
Arquivo de configuração de microgateway (~./edgemicro/config.yaml
) da seguinte maneira. A sequência é
muito importante. Ele define a ordem em que os gerenciadores de plug-in são executados.
plugins: dir: ../plugins sequence: - plugin-1 - plugin-2 - plugin-3
4. Examinar a saída de depuração
Agora, vejamos a saída produzida quando esses plug-ins são chamados. Existem alguns pontos importantes a serem observados:
- A sequência do plug-in no arquivo de configuração do Edge Microgateway
(
~./edgemicro/config.yaml
) especifica a ordem em que os manipuladores de eventos são chamou. - Os gerenciadores de solicitação são chamados em ordem crescente, ou seja, a ordem em que aparecem na sequência do plug-in: 1, 2, 3).
- Os gerenciadores de resposta são chamados em ordem decrescente: 3, 2, 1.
- O gerenciador
ondata_response
é chamado uma vez para cada bloco de dados que chega. Neste exemplo (saída mostrada abaixo), dois blocos são recebidos.
Este é um exemplo de saída de depuração produzida quando esses três plug-ins estão em uso e uma solicitação é enviada pelo Edge Microgateway. Observe a ordem em que os manipuladores são chamados:
plugin-1: onrequest plugin-2: onrequest plugin-3: onrequest plugin-1: onend_request plugin-2: onend_request plugin-3: onend_request plugin-3: ondata_response 931 plugin-2: ondata_response 931 plugin-1: ondata_response 931 plugin-3: ondata_response 1808 plugin-3: onend_response plugin-2: ondata_response 1808 plugin-2: onend_response plugin-1: ondata_response 1808 plugin-1: onend_response
Resumo
É muito importante entender a ordem em que os gerenciadores de plug-ins são chamados ao tentar implementar a funcionalidade de plug-in personalizado, como acumular e transformar solicitações ou respostas dados.
Lembre-se de que os manipuladores de solicitação são executados na ordem em que os plug-ins são especificados no arquivo de configuração do Edge Microgateway, e os gerenciadores de resposta são executados ordem oposta.
Sobre o uso de variáveis globais em plug-ins
Todas as solicitações para o Edge Microgateway são enviadas para a mesma instância de um plug-in. portanto, o estado da segunda solicitação de outro cliente vai substituir o primeiro. O único lugar seguro para salvar o estado do plug-in é armazenando-o em uma propriedade no objeto de solicitação ou resposta (cuja sua vida útil seja limitada ao da solicitação).
Reescrever URLs de destino em plug-ins
Adicionado à: v2.3.3
Você pode modificar dinamicamente o URL de destino padrão em um plug-in modificando essas variáveis no código do seu plug-in: req.targetHostname e req.targetPath.
Adicionado em: v2.4.x
Também é possível substituir a porta do endpoint de destino e escolher entre HTTP e HTTPS. Modificar estes variáveis no código do plug-in: req.targetPort, e req.targetSecure. Para escolher HTTPS, defina req.targetSecure como true; Para HTTP, defina-o como false. Se você definir req.targetSecure como verdade, veja esta discussão tópico para mais informações.
Plug-ins de exemplo
Esses plug-ins são fornecidos com a instalação do Edge Microgateway. Você pode encontrá-los na Instalação do Edge Microgateway aqui:
[prefix]/lib/node_modules/edgemicro/plugins
em que [prefix]
é o diretório do prefixo npm
conforme
descritos em "Onde o Edge Microgateway está instalado" em Como instalar o Edge
Microgateway
accumulate-request
Esse plug-in acumula blocos de dados do cliente em uma propriedade de matriz anexada à objeto de solicitação. Quando todos os dados da solicitação são recebidos, a matriz é concatenada em um que é transmitido para o próximo plug-in na sequência. Este plug-in precisa ser o primeiro na sequência para que os plug-ins subsequentes recebam os dados de solicitação acumulados.
module.exports.init = function(config, logger, stats) { function accumulate(req, data) { if (!req._chunks) req._chunks = []; req._chunks.push(data); } return { ondata_request: function(req, res, data, next) { if (data && data.length > 0) accumulate(req, data); next(null, null); }, onend_request: function(req, res, data, next) { if (data && data.length > 0) accumulate(req, data); var content = null; if (req._chunks && req._chunks.length) { content = Buffer.concat(req._chunks); } delete req._chunks; next(null, content); } }; }
accumulate-response
Esse plug-in acumula blocos de dados do destino em uma propriedade de matriz anexada ao objeto de resposta. Quando todos os dados de resposta são recebidos, a matriz é concatenada em um que é transmitido para o próximo plug-in na sequência. Como este plug-in opera em de resposta, que são processadas em ordem inversa, posicione-o como o último plug-in na sequência.
module.exports.init = function(config, logger, stats) { function accumulate(res, data) { if (!res._chunks) res._chunks = []; res._chunks.push(data); } return { ondata_response: function(req, res, data, next) { if (data && data.length > 0) accumulate(res, data); next(null, null); }, onend_response: function(req, res, data, next) { if (data && data.length > 0) accumulate(res, data); var content = Buffer.concat(res._chunks); delete res._chunks; next(null, content); } }; }
plug-in "header-uppercase"
As distribuições do Edge Microgateway incluem um plug-in de amostra chamado
<microgateway-root-dir>/plugins/header-uppercase
: A amostra inclui comentários
descrevendo cada um dos gerenciadores de função. Este exemplo faz uma transformação de dados simples da
resposta de destino e adiciona cabeçalhos personalizados à solicitação do cliente e à resposta de destino.
Aqui está o código-fonte de
<microgateway-root-dir>/plugins/header-uppercase/index.js
:
'use strict'; var debug = require('debug')('plugin:header-uppercase'); // required module.exports.init = function(config, logger, stats) { var counter = 0; return { // indicates start of client request // request headers, url, query params, method should be available at this time // request processing stops (and a target request is not initiated) if // next is called with a truthy first argument (an instance of Error, for example) onrequest: function(req, res, next) { debug('plugin onrequest'); req.headers['x-foo-request-id'] = counter++; req.headers['x-foo-request-start'] = Date.now(); next(); }, // indicates start of target response // response headers and status code should be available at this time onresponse: function(req, res, next) { debug('plugin onresponse'); res.setHeader('x-foo-response-id', req.headers['x-foo-request-id']); res.setHeader('x-foo-response-time', Date.now() - req.headers['x-foo-request-start']); next(); }, // chunk of request body data received from client // should return (potentially) transformed data for next plugin in chain // the returned value from the last plugin in the chain is written to the target ondata_request: function(req, res, data, next) { debug('plugin ondata_request ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }, // chunk of response body data received from target // should return (potentially) transformed data for next plugin in chain // the returned value from the last plugin in the chain is written to the client ondata_response: function(req, res, data, next) { debug('plugin ondata_response ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }, // indicates end of client request onend_request: function(req, res, data, next) { debug('plugin onend_request'); next(null, data); }, // indicates end of target response onend_response: function(req, res, data, next) { debug('plugin onend_response'); next(null, data); }, // error receiving client request onerror_request: function(req, res, err, next) { debug('plugin onerror_request ' + err); next(); }, // error receiving target response onerror_response: function(req, res, err, next) { debug('plugin onerror_response ' + err); next(); }, // indicates client connection closed onclose_request: function(req, res, next) { debug('plugin onclose_request'); next(); }, // indicates target connection closed onclose_response: function(req, res, next) { debug('plugin onclose_response'); next(); } }; }
transformar em maiúsculas
Este é um plug-in de transformação geral que você pode modificar para fazer qualquer tipo de a transformação desejada. Esse exemplo simplesmente transforma a resposta e os dados da solicitação em maiúsculas.
*/ module.exports.init = function(config, logger, stats) { // perform content transformation here // the result of the transformation must be another Buffer function transform(data) { return new Buffer(data.toString().toUpperCase()); } return { ondata_response: function(req, res, data, next) { // transform each chunk as it is received next(null, data ? transform(data) : null); }, onend_response: function(req, res, data, next) { // transform accumulated data, if any next(null, data ? transform(data) : null); }, ondata_request: function(req, res, data, next) { // transform each chunk as it is received next(null, data ? transform(data) : null); }, onend_request: function(req, res, data, next) { // transform accumulated data, if any next(null, data ? transform(data) : null); } }; }