Обзор
Предположим, что есть услуга, имеющая следующие функции:
- Он предоставляет конечную точку отдыха, получающее список запросов.
- Он параллельно приводит к сервису SOAP, один раз в элемент в списке запросов.
- Возвращает преобразованный результат из XML в JSON.
Исходный код этого сервиса может выглядеть примерно так, используя Node.js, Express и Руководство по стилю Airbnb JavaScript :
'use strict';
const { soap } = require('strong-soap');
const expressApp = require('express')();
const bodyParser = require('body-parser');
const url = 'http://www.dneonline.com/calculator.asmx?WSDL';
const clientPromise = new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => err ? reject(err) : resolve(client))
));
expressApp.use(bodyParser.json())
.post('/parallel-soap-invoke', (req, res) => (clientPromise.then(client => ({ client, requests: req.body }))
.then(invokeOperations)
.then(results => res.status(200).send(results))
.catch(({ message: error }) => res.status(500).send({ error }))
))
.listen(3000, () => console.log('Waiting for incoming requests.'));
const invokeOperations = ({ client, requests }) => (Promise.all(requests.map(request => (
new Promise((resolve, reject) => client.Add(request, (err, result) => (
err ? reject(err) : resolve(result))
))
))));Запрос образца:
POST /parallel-soap-invoke
[
{
"intA": 1,
"intB": 2
},
{
"intA": 3,
"intB": 4
},
{
"intA": 5,
"intB": 6
}
]Образец ответа:
HTTP/1.1 200
[
{
"AddResult": 3
},
{
"AddResult": 7
},
{
"AddResult": 11
}
]Испытания показывают, что один прямой запрос на сервис SOAP с помощью SOAPUI занимает ~ 430 мс (откуда я расположен в Чили). Отправка трех запросов (как показано выше) занимает ~ 400 мс для вызовов на экспресс-службу (кроме первого, которая получает WSDL и создает клиент).
Почему больше запросов занять меньше времени? В основном потому, что XML не сильно подтвержден, так как он находится в обычном мыте, поэтому, если эта мягкая проверка не соответствует вашим ожиданиям, вы должны рассмотреть дополнительные функции или решения.
Интересно, как бы он выглядел, используя Async/await ? Здесь вы идете (результаты одинаковы):
'use strict';
const { soap } = require('strong-soap');
const expressApp = require('express')();
const bodyParser = require('body-parser');
const url = 'http://www.dneonline.com/calculator.asmx?WSDL';
const clientPromise = new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => err ? reject(err) : resolve(client))
));
expressApp.use(bodyParser.json())
.post('/parallel-soap-invoke', async (req, res) => {
try {
res.status(200).send(await invokeOperations(await clientPromise, req.body));
} catch ({message: error}) {
res.status(500).send({ error });
}
})
.listen(3000, () => console.log('Waiting for incoming requests.'));
const invokeOperations = (client, requests) => (Promise.all(requests.map(request => (
new Promise((resolve, reject) => client.Add(request, (err, result) => (
err ? reject(err) : resolve(result))
))
))));Следующее изображение предоставляет концепцию того, как работает код:
Эта статья направлена на то, чтобы показать простоту использования JavaScript для задач в корпоративном мире, таких как призывающие услуги SOAP. Если вы знакомы с JavaScript, это в основном просто Обещание. Все На вершине пары похищенных обратных вызовов под экспресс-конечной точкой. Вы можете перейти непосредственно в раздел 4 ( Бонус-трек ) Если вы думаете, что это может быть полезно для вас.
Если вы за пределами мира JavaScript, я думаю, что 24 строки кода для трех функций, которые я упомянул в начале, очень хорошая сделка. Теперь я пойду в детали.
1. Экспресс раздел
Давайте начнем с Code, связанного с Express, минимальным и гибким и гибким веб-приложением Node.js. Это довольно просто, и вы можете найти его нигде, поэтому я дам суммированное описание.
'use strict';
// Express framework.
const express = require('express');
// Creates an Express application.
const app = express();
/**
* Creates a GET (which is defined by the method invoked on 'app') endpoint,
* having 'parallel-soap-invoke' as entry point.
* Each time a GET request arrives at '/parallel-soap-invoke', the function passed
* as the second parameter from app.get will be invoked.
* The signature is fixed: the request and response objects.
*/
app.get('/parallel-soap-invoke', (_, res) => {
// HTTP status of the response is set first and then the result to be sent.
res.status(200).send('Hello!');
});
// Starts 'app' and sends a message when it's ready.
app.listen(3000, () => console.log('Waiting for incoming requests.'));Результат:
GET /parallel-soap-invoke HTTP/1.1 200 Hello!
Теперь нам нужно будет обрабатывать объект, отправленный по почте. Экспресс Тело-парсер обеспечивает легкий доступ к корпусу запроса:
'use strict';
const expressApp = require('express')(); // Compressing two lines into one.
const bodyParser = require('body-parser'); // Several parsers for HTTP requests.
expressApp.use(bodyParser.json()) // States that 'expressApp' will use JSON parser.
// Since each Express method returns the updated object, methods can be chained.
.post('/parallel-soap-invoke', (req, res) => {
/**
* As an example, the same request body will be sent as response with
* a different HTTP status code.
*/
res.status(202).send(req.body); // req.body will have the parsed object
})
.listen(3000, () => console.log('Waiting for incoming requests.'));POST /parallel-soap-invoke
content-type: application/json
[
{
"intA": 1,
"intB": 2
},
{
"intA": 3,
"intB": 4
},
{
"intA": 5,
"intB": 6
}
]
HTTP/1.1 202
[
{
"intA": 1,
"intB": 2
},
{
"intA": 3,
"intB": 4
},
{
"intA": 5,
"intB": 6
}
]
Итак, длинная история короткая: настройте приложение Express, и как только у вас есть результат, отправьте его через res и вуаля.
2. Мыльная секция
У этого будет несколько шагов, чем предыдущий раздел. Основная идея заключается в том, что для создания вызовов мыла параллельно я буду использовать Обещание. Все Отказ В состоянии использовать Обещание. Все , вызов на услуги SOAP должен быть обработан в обещании, что не так для Сильное мыло Отказ Этот раздел покажет, как преобразовать регулярные обратные вызовы из Сильное мыло в обещания, а затем положить Обещание. Все более того.
Следующий код будет использовать самый простой пример из Сильное мыло «S документация Отказ Я просто упростить это немного и использовать тот же WSDL Мы видим (Я не использовал тот же WSDL, указанный в сильноточных мыл документации «s, так что WSDL не работает больше):
'use strict';
// The SOAP client library.
var { soap } = require('strong-soap');
// WSDL we'll be using through the article.
var url = 'http://www.dneonline.com/calculator.asmx?WSDL';
// Hardcoded request
var requestArgs = {
"intA": 1,
"intB": 2,
};
// Creates the client which is returned in the callback.
soap.createClient(url, {}, (_, client) => (
// Callback delivers the result of the SOAP invokation.
client.Add(requestArgs, (_, result) => (
console.log(`Result: ${"\n" + JSON.stringify(result)}`)
))
));$ node index.js
Result:
{"AddResult":3}Я буду преобразовывать это в обещания, и я пройду все обратные вызовы, один за другим, ради примера. Таким образом, процесс перевода будет Crystal Clear для вас:
'use strict';
var { soap } = require('strong-soap');
var url = 'http://www.dneonline.com/calculator.asmx?WSDL';
var requestArgs = {
"intA": 1,
"intB": 2,
};
/**
* A function that will return a Promise which will return the SOAP client.
* The Promise receives as parameter a function having two functions as parameters:
* resolve & reject.
* So, as soon as you got a result, call resolve with the result,
* or call reject with some error otherwise.
*/
const createClient = () => (new Promise((resolve, reject) => (
// Same call as before, but I'm naming the error parameter since I'll use it.
soap.createClient(url, {}, (err, client) => (
/**
* Did any error happen? Let's call reject and send the error.
* No? OK, let's call resolve sending the result.
*/
err ? reject(err) : resolve(client)
))))
);
/**
* The above function is invoked.
* The Promise could have been inlined here, but it's more understandable this way.
*/
createClient().then(
/**
* If at runtime resolve is invoked, the value sent through resolve
* will be passed as parameter for this function.
*/
client => (client.Add(requestArgs, (_, result) => (
console.log(`Result: ${"\n" + JSON.stringify(result)}`)
))),
// Same as above, but in this case reject was called at runtime.
err => console.log(err),
);Призыв Узел index.js Получает тот же результат, что и раньше. Следующий обратный вызов:
'use strict';
var { soap } = require('strong-soap');
var url = 'http://www.dneonline.com/calculator.asmx?WSDL';
var requestArgs = {
"intA": 1,
"intB": 2,
};
const createClient = () => (new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => (
err ? reject(err) : resolve(client)
))))
);
/**
* Same as before: do everything you need to do; once you have a result,
* resolve it, or reject some error otherwise.
* invokeOperation will replace the first function of .then from the former example,
* so the signatures must match.
*/
const invokeOperation = client => (new Promise((resolve, reject) => (
client.Add(requestArgs, (err, result) => (
err ? reject(err) : resolve(result)
))
)));
/**
* .then also returns a Promise, having as result the value resolved or rejected
* by the functions that were passed as parameters to it. In this case, the second .then
* will receive the value resolved/rejected by invokeOperation.
*/
createClient().then(
invokeOperation,
err => console.log(err),
).then(
result => console.log(`Result: ${"\n" + JSON.stringify(result)}`),
err => console.log(err),
);Узел index.js ? Все тот же. Давайте обернуем эти обещания в функции, чтобы подготовить код для вызова его внутри Express конечной точки. Это также упрощает обработку ошибок:
'use strict';
var { soap } = require('strong-soap');
var url = 'http://www.dneonline.com/calculator.asmx?WSDL';
var requestArgs = {
"intA": 1,
"intB": 2,
};
const createClient = () => (new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => (
err ? reject(err) : resolve(client)
))))
);
const invokeOperation = client => (new Promise((resolve, reject) => (
client.Add(requestArgs, (err, result) => (
err ? reject(err) : resolve(result)
))
)));
const processRequest = () => createClient().then(invokeOperation);
/**
* .catch() will handle any reject not handled by a .then. In this case,
* it will handle any reject called by createClient or invokeOperation.
*/
processRequest().then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`))
.catch(({ message }) => console.log(message));Бьюсь об заклад, вы можете угадать результат Узел index.js. .
Что произойдет, если несколько последующих звонков сделаны? Мы узнаем со следующим кодом:
'use strict';
var { soap } = require('strong-soap');
var url = 'http://www.dneonline.com/calculator.asmx?WSDL';
var requestArgs = {
"intA": 1,
"intB": 2,
};
const createClient = () => (new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => {
if (err) {
reject(err);
} else {
// A message is displayed each time a client is created.
console.log('A new client is being created.');
resolve(client);
}
})))
);
const invokeOperation = client => (new Promise((resolve, reject) => (
client.Add(requestArgs, (err, result) => (
err ? reject(err) : resolve(result)
))
)));
const processRequest = () => createClient().then(invokeOperation)
processRequest().then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`))
.catch(({ message }) => console.log(message));
processRequest().then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`))
.catch(({ message }) => console.log(message));
processRequest().then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`))
.catch(({ message }) => console.log(message));$ node index.js
A new client is being created.
A new client is being created.
Result:
{"AddResult":3}
A new client is being created.
Result:
{"AddResult":3}
Result:
{"AddResult":3}Не хорошо, как создаются несколько клиентов. В идеале клиент должен быть кэширован и повторно использован. Есть два основных способа достижения этого:
- Вы можете создать переменную за пределами обещания и кэшируйте клиента, как только у вас есть (как раз перед разрешением его). Давайте назовем это
CachedClientОтказ Но в этом случае вам придется вручную разобраться с звонками наCreateClient ()Сделано между первым его называемым и до того, как первый клиент будет решен. Вам придется проверить, еслиCachedClientЯвляется ли ожидаемая стоимость, или вам придется проверить, разрешено ли обещание или нет, или вам придется поставить свой эмиттер событий, чтобы знать, когдаCachedClientготово. Первый раз, когда я писал код для этого, я использовал этот подход, и я оказался жить с тем, что каждый один звонок сделан до первогоCreateClient (). РешитьПерезаписатьCachedClientОтказ Если проблема не в том, что ясно, дайте мне знать, и я напишу код и примеры. - Обещания имеют очень классную функцию ( см. Документацию MDN, раздел «Возвращение» ): Если вы звоните
.then ()На решении/отклоненном обещании он вернет то же самое значение, которое было разрешено/отклонено, без обработки снова. На самом деле, очень технически, это будет очень такая же ссылка на объект.
Второй подход гораздо проще реализовать, поэтому связанный код является следующим:
'use strict';
var { soap } = require('strong-soap');
var url = 'http://www.dneonline.com/calculator.asmx?WSDL';
var requestArgs = {
"intA": 1,
"intB": 2,
};
// createClient function is removed.
const clientPromise = (new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => {
if (err) {
reject(err);
} else {
console.log('A new client is being created.');
resolve(client);
}
})))
);
const invokeOperation = client => (new Promise((resolve, reject) => (
client.Add(requestArgs, (err, result) => (
err ? reject(err) : resolve(result)
))
)));
// clientPromise is called instead getClient().
clientPromise.then(invokeOperation)
.then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`))
.catch(({ message }) => console.log(message));
clientPromise.then(invokeOperation)
.then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`))
.catch(({ message }) => console.log(message));
clientPromise.then(invokeOperation)
.then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`))
.catch(({ message }) => console.log(message));$ node index.js
A new client is being created.
Result:
{"AddResult":3}
Result:
{"AddResult":3}
Result:
{"AddResult":3}Наконец, для этого раздела давайте сделаем код с помощью нескольких параллельных вызовов. Это будет легко:
- Для обработки нескольких параллельных вызовов нам понадобится
Обещание. ВсеОтказ Обещание. ВсеИмеет один параметр: массив обещаний. Поэтому мы будем преобразовывать список запросов в список обещаний. В настоящее время код преобразует один запрос в одно обещание (Завод), поэтому код просто нуждается в.картадля достижения этой цели.
'use strict';
var { soap } = require('strong-soap');
var url = 'http://www.dneonline.com/calculator.asmx?WSDL';
// Hardcoded list of requests.
var requestsArgs = [
{
"intA": 1,
"intB": 2,
},
{
"intA": 3,
"intB": 4,
},
{
"intA": 5,
"intB": 6,
},
];
const clientPromise = (new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => err ? reject(error) : resolve(client))
)));
// Promise.all on top of everything.
const invokeOperation = client => (Promise.all(
// For each request, a Promise is returned.
requestsArgs.map(requestArgs => new Promise((resolve, reject) => (
// Everything remains the same here.
client.Add(requestArgs, (err, result) => (
err ? reject(err) : resolve(result)
))
)))
));
clientPromise.then(invokeOperation)
.then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`))
.catch(({ message }) => console.log(message));$ node index.js
Result:
[{"AddResult":3},{"AddResult":7},{"AddResult":11}]3. Положить все вместе
Это довольно легко – это просто сборка последнего кода из каждого предыдущего раздела:
'use strict';
const { soap } = require('strong-soap');
const expressApp = require('express')();
const bodyParser = require('body-parser');
const url = 'http://www.dneonline.com/calculator.asmx?WSDL';
const clientPromise = new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => err ? reject(err) : resolve(client))
));
expressApp.use(bodyParser.json())
.post('/parallel-soap-invoke', (req, res) => (clientPromise.then(invokeOperations)
.then(results => res.status(200).send(results))
.catch(({ message: error }) => res.status(500).send({ error }))
))
.listen(3000, () => console.log('Waiting for incoming requests.'));
// Adding req.body instead of hardcoded requests.
const invokeOperations = client => Promise.all(req.body.map(request => (
new Promise((resolve, reject) => client.Add(request, (err, result) => (
err ? reject(err) : resolve(result))
))
)));POST /parallel-soap-invoke
[
{
"intA": 1,
"intB": 2
},
{
"intA": 3,
"intB": 4
},
{
"intA": 5,
"intB": 6
}
]
HTTP/1.1 500
{
"error": "req is not defined"
}Хммм … не хороший результат, так как я не ожидал ошибки вообще. Проблема в том, что Инволюция не имеет req в его объеме. Первая мысль может быть «просто добавить его к подписи. ” Но это невозможно, так как подпись соответствует результату от предыдущего обещания, и это обещание не возвращает req только возвращает клиент . Но что, если мы добавим промежуточное обещание, чья единственная цель впрыскивает это значение?
'use strict';
const { soap } = require('strong-soap');
const expressApp = require('express')();
const bodyParser = require('body-parser');
const url = 'http://www.dneonline.com/calculator.asmx?WSDL';
const clientPromise = new Promise((resolve, reject) => (
soap.createClient(url, {}, (err, client) => err ? reject(err) : resolve(client))
));
expressApp.use(bodyParser.json())
.post('/parallel-soap-invoke', (req, res) => (
/**
* After clientPromise.then, where client is received, a new Promise is
* created, and that Promise will resolve an object having two properties:
* client and requests.
*/
clientPromise.then(client => ({ client, requests: req.body }))
.then(invokeOperations)
.then(results => res.status(200).send(results))
.catch(({ message: error }) => res.status(500).send({ error }))
))
.listen(3000, () => console.log('Waiting for incoming requests.'));
/**
* Since the shape of the object passed to invokeOperations changed, the signature has
* to change to reflect the shape of the new object.
*/
const invokeOperations = ({ client, requests }) => Promise.all(requests.map(request => (
new Promise((resolve, reject) => client.Add(request, (err, result) => (
err ? reject(err) : resolve(result))
))
)));Результаты точно такие же, как те, что на резюме.
4. Бонус трек
Общее мыло к JSON Converter для параллельного мыла, вызывающего. Код знаком, основанный на том, что вы видели в прежних разделах. Как насчет этого?
'use strict';
const { soap } = require('strong-soap');
const expressApp = require('express')();
const bodyParser = require('body-parser');
const clientPromises = new Map();
expressApp.use(bodyParser.json())
.post('/parallel-soap-invoke', ({ body: { wsdlUrl, operation, requests } }, res) => (
getClient(wsdlUrl).then(client => ({ client, operation, requests }))
.then(invokeOperations)
.then(results => res.status(200).send(results))
.catch(({ message: error }) => res.status(500).send({ error }))
))
.listen(3000, () => console.log('Waiting for incoming requests.'));
const getClient = wsdlUrl => clientPromises.get(wsdlUrl)
|| (clientPromises.set(wsdlUrl, new Promise((resolve, reject) => (
soap.createClient(wsdlUrl, {}, (err, client) => err ? reject(err) : resolve(client))
))).get(wsdlUrl));
const invokeOperations = ({ client, operation, requests }) => (Promise.all(requests.map(request => (
new Promise((resolve, reject) => client[operation](request, (err, result) => (
err ? reject(err) : resolve(result))
))
))));Первый пример использования:
POST /parallel-soap-invoke
content-type: application/json
{
"wsdlUrl": "http://www.dneonline.com/calculator.asmx?WSDL",
"operation": "Add",
"requests": [
{
"intA": 1,
"intB": 2
},
{
"intA": 3,
"intB": 4
},
{
"intA": 5,
"intB": 6
}
]
}
HTTP/1.1 200
[
{
"AddResult": 3
},
{
"AddResult": 7
},
{
"AddResult": 11
}
]
Второй пример использования:
POST /parallel-soap-invoke
content-type: application/json
{
"wsdlUrl": "http://ws.cdyne.com/ip2geo/ip2geo.asmx?wsdl",
"operation": "ResolveIP",
"requests": [
{
"ipAddress": "8.8.8.8",
"licenseKey": ""
},
{
"ipAddress": "8.8.4.4",
"licenseKey": ""
}
]
}
HTTP/1.1 200
[
{
"ResolveIPResult": {
"Country": "United States",
"Latitude": 37.75101,
"Longitude": -97.822,
"AreaCode": "0",
"HasDaylightSavings": false,
"Certainty": 90,
"CountryCode": "US"
}
},
{
"ResolveIPResult": {
"Country": "United States",
"Latitude": 37.75101,
"Longitude": -97.822,
"AreaCode": "0",
"HasDaylightSavings": false,
"Certainty": 90,
"CountryCode": "US"
}
}
]Вы собираетесь через Цифровое отделение ? В архитектуре Full-Stack JavaScript на вершине старых услуг этот артефакт может помочь вам инкапсулировать все сервисы SOAP, расширяя их и выставляем только JSON. Вы можете даже немного изменить этот код, чтобы вызвать несколько различных сервисов SOAP одновременно (что должно быть просто дополнительным .map и .уменьшать , как я вижу это прямо сейчас). Или вы могли бы инкапсулировать WSDL, Вашего предприятия в базу данных и ссылаться на них на основе кода или какой-либо идентификатор. Это было бы только один или два дополнительных обещания в цепочку.
Оригинал: “https://www.freecodecamp.org/news/an-express-service-for-parallel-soap-invocation-in-under-25-lines-of-code-b7eac725702e/”