http使用restful代理soap

简介: 作为建立在http协议之上的数据协议,soap定义了数据报文的格式与规范与应用程序的调用方式(RPC模式),隐藏了网络传输的细节,这里采用restful的风格来请求webservice服务

标题:http使用restful代理soap协议

引言:作为建立在http协议之上的数据协议,soap定义了数据报文的格式与规范与应用程序的调用方式(RPC模式),即隐藏了网络传输的细节。

1、soap

simple object accessProtocol:简单的对象访问协议,采用XML格式来作为数据报文,拥有着RPC的特性,所以注定适用于早期的分布式计算环境中。

1.1、对比http

两个概念,http是应用协议,soap是基于http之上的数据协议。http只负责数据的传递,无论数据是图片,网页,文件……,在流转链路中,统一以文本方式处理。soap定义了一种规约:如何把请求&响应传递的XML文本数据,解析成对象。

总结下来就是:soap = rpc模式 + http传输 + xml报文,硬说区别可以列举如下三点:

  • http是一种超文本传输协议,规定的传输内容皆为文本;
  • soap是http的高级应用,以结构化的xml数据来作为报文;
  • server 与 client存在格式化解析报文,故完整的请求流程会牺牲一点性能。

1.2、webservice

soap基础上的实际应用,早期的分布式应用程序。下面以java为例,分别搭建server 和client。

1.2.1、server

采用JAX-WS框架快速构建的方式搭建一个webservice server。

对外提供一个API:sayHelloWorldFrom

package example;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;

/**
 * @ClassName ${NAME}
 * @Description TODO
 * @Date 2022/8/15 2:15 H
 * @Author ikejcwang
 * @Version 1.0.0
 **/
@WebService()
public class HelloWorld {

    @WebMethod
    public String sayHelloWorldFrom(String from) {
        String result = "Hello, world, from " + from;
        System.out.println(result);
        return result;
    }

    public static void main(String[] argv) {
        Object implementor = new HelloWorld();
        String address = "http://localhost:9000/HelloWorld";
        Endpoint.publish(address, implementor);
    }
}

启动main函数后,浏览器打开链接:http://localhost:9000/HelloWorld?wsdl,可以查阅server对外提供的wsdl文档

1.2.2、wsdl

框架内置了wsdl生成,作为介绍该webservice server的功能特点,对外提供了哪些函数API,这些函数的参数结构,约束条件,参数如何组装成soap报文。方便客户端无障碍调用;

1.2.3、client

采用JAX-WS框架针对特定的wsdlURL,快速自动生成客户端的class。如下所示:

└── src
    ├── example
    │   └── HelloWorldClient.java
    └── vip
        └── wangjc
            ├── HelloWorld.class
            ├── HelloWorld.java
            ├── HelloWorld.wsdl
            ├── HelloWorldService.class
            ├── HelloWorldService.java
            ├── ObjectFactory.class
            ├── ObjectFactory.java
            ├── SayHelloWorldFrom.class
            ├── SayHelloWorldFrom.java
            ├── SayHelloWorldFromResponse.class
            ├── SayHelloWorldFromResponse.java
            ├── package-info.class
            └── package-info.java

main函数如下:

package example;

import vip.wangjc.HelloWorld;
import vip.wangjc.HelloWorldService;

/**
 * @ClassName ${NAME}
 * @Description TODO
 * @Date 2022/8/15 3:07 H
 * @Author ikejcwang
 * @Version 1.0.0
 **/
public class HelloWorldClient {
    public static void main(String[] argv) {
        HelloWorld service = new HelloWorldService().getHelloWorldPort();
        //invoke business method
        String result = service.sayHelloWorldFrom("haha ikejcwang");    // 远程调用的逻辑实现
        System.out.println(result);

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.2.4、抓包

启动server,然后继续启动client,通过抓包采集下client发送的报文,如图所示

soap_抓包.png

根据抓包信息来看,可以推测发现,client是先获取wsdl文档,将wsdl文档缓存在当前内存中,然后根据wsdl文档提供的规约来将需要调用的对象组装成XML文本,然后发起http请求。

为什么会说它会将wsdl文档缓存起来?可以修改client->main函数,调用两次sayHelloWorldFrom,会发现只有在最开始时,或是程序启动时访问了/url?wsdl链接,后续都是直接发起http业务请求的;

数据报文如下:

<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope
    xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Body>
        <ns2:sayHelloWorldFrom
            xmlns:ns2="http://example/">
            <arg0>haha ikejcwang</arg0>
        </ns2:sayHelloWorldFrom>
    </S:Body>
</S:Envelope>

1.2.5、post调用?

既然知道,它是基于http协议发起调用的,也通过抓包拿到了它的请求报文,按理,postman或者任意的http client都可以发起调用;

soap_postman.png

如上所示,调用正常,拿到的原始响应报文(XML结构)。webservice client自行会根据wsdl文档对原始响应报文做XML->对象的处理。

2、http Rest Proxy

既然上述知道,是先通过获取wsdl文档完成后续的传输文本编解码,那么应该也可以搭一个http代理,在代理内部完成编解码的任务,支持外部json数据,或者xml原始数据,通过这个http代理server都能成功请求soap server。

2.1、实现逻辑

我们选择在http server内部构建soap client对象,对外选择屏蔽wsdl,需要一个配置文件,来管理多个soap server,配置文件的结构如下:

{
  "userManager": {
    "server": "http://localhost:9000/HelloWorld"
  }
}

http client发起请求时,URL结构上一级path代表userManager,锁定需要访问的哪个soap server,二级path代表method`,调用指定soap server的哪个函数名。

2.2、http server

如下代码,思路应该比较简洁,

  1. 启动入口为start,先初始化soap配置文件,再开启http服务,监听请求;
  2. 初始化soapClient,遍历config配置,依次初始化后存入到soapCache对象中;
  3. 开启http服务,并监听请求事件,请求触发时,如果soapConfig为空,就返回500;
  4. 获取请求的pathname,按/分割,要求必须/${userManager}/${methodName},结尾不能带/,否则依次返回500&错误信息;
  5. 从请求中读取请求报文;
  6. 拿到报文body 和 二级path的methodName,根据一级path筛选的soap对象,去调用soap服务;
  7. 做出http响应,200 & 500,正常报文 & error info
/**
 * create by ikejcwang on 2022.03.07.
 * 注:这只是一个demo,没有适配高并发,如果想要引用的话,可以根据nodejs多进程实现方案来具体改造。
 */
'use strict';
const http = require('http');
const nodeUtil = require('util');
const URL = require('url');
const SoapClient = require('./SoapClient');
const settings = require('./settings').settings;
const configs = require('./settings').configs;
let soapCache = {}

start();

/**
 * 启动入口
 */
function start() {
    initSoapConfig();
    startHttpServer();
}

/**
 * 初始化Soap配置
 */
function initSoapConfig() {
    if (configs && Object.keys(configs).length > 0) {
        for (let key in configs) {
            soapCache[key] = new SoapClient(configs[key]);
        }
    }
}

/**
 * 启动http服务
 */
function startHttpServer() {
    let server = http.createServer();
    server.on('request', listenRequestEvent);
    server.on('close', () => {
        console.log('http Server has Stopped At:' + settings['bindPort'])
    });
    server.on('error', err => {
        console.log('http Server error:' + err.toString());
        setTimeout(() => {
            process.exit(1);
        }, 3000);
    });
    server.listen(settings['bindPort'], settings['bindIP'], settings['backlog'] || 8191, () => {
        console.log('Started Http Server At: ' + settings['bindIP'] + ':' + settings['bindPort'])
    })
}

/**
 * 监听request事件
 * @param request
 * @param response
 */
async function listenRequestEvent(request, response) {
    request.on('aborted', () => {
        console.error('aborted: client request aborted')
    });
    request.on('finish', () => {
        console.log('request has finished');
    })
    request.on('error', (err) => {
        console.log(`error event: ${nodeUtil.inspect(err)}`)
    })
    try {
        if (!configs || Object.keys(configs).length < 1) {
            response.statusCode = 500;
            response.setHeader('content-type', 'text/plain; charset=utf-8');
            response.end('No Soap Config');
            return;
        }
        let sourceUrl = URL.parse(request.url, true);
        let pathArr = sourceUrl.pathname.split('/').splice(1);
        if (pathArr.length < 2 || !pathArr[pathArr.length - 1]) {
            response.statusCode = 500;
            response.setHeader('content-type', 'text/plain; charset=utf-8');
            response.end('Unable to resolve soapMethod from pathname');
            return;
        }

        let soapConfigName = pathArr.splice(0, pathArr.length - 1).join('/');
        let soapMethod = pathArr[pathArr.length - 1];
        let soapObj = soapCache[soapConfigName];
        if (!soapObj) {
            response.statusCode = 500;
            response.setHeader('content-type', 'text/plain; charset=utf-8');
            response.end(`Unable to resolve ${soapConfigName} from config`);
            return;
        }
        let bodyChunk = [];
        request.on('data', chunk => {
            bodyChunk.push(chunk);
        });
        request.on('end', () => {
            let body = JSON.parse(bodyChunk.toString());
            try {
                soapObj.request(body, soapMethod).then(resBody => {
                    request.resBody_len = JSON.stringify(resBody).length;
                    request.duration = Date.now() - request.startTime;
                    response.statusCode = 200;
                    response.setHeader('content-type', 'application/json; charset=utf-8');
                    response.end(Buffer.from(JSON.stringify(resBody)));
                }).catch(err => {
                    request.errMsg = err.toString();
                    request.duration = Date.now() - request.startTime;
                    response.statusCode = 500;
                    response.setHeader('content-type', 'text/html; charset=utf-8');
                    response.end(Buffer.from(err.toString()));
                });

            } catch (e) {
                request.errMsg = e.toString();
                response.statusCode = 500;
                response.setHeader('content-type', 'text/html; charset=utf-8');
                response.end(Buffer.from(e.toString()));
            }
        });
    } catch (e) {
        console.log(`request_error: ${nodeUtil.inspect(e)}`);
        response.statusCode = 502;
        response.end('ike httpToSoap proxy error');
    }
}

2.3、soapClient

发现一个soap的npm包,自动实现了编解码的各种任务,这里在它的基础之上再简单封装一层。

  1. start启动时,根据遍历的config配置文件依次完成SoapClient的初始化;
  2. 调用时,按需传递body和method,完成最终调用,给出响应即可;
  3. rawResponse,rawRequest都是原始的XML结构报文,可以自行拆解debug查看;
/**
 * create by ikejcwang on 2022.03.07.
 */
'use strict';
const soap = require('soap');
const soapOptions = {
    disableCache: true,
    escapeXML: false
}

/**
 * 构造自定义的soap类
 */
class SoapClient {

    constructor(config) {
        this.server = config.server;
        this.init()
    }

    init() {
        soap.createClientAsync(`${this.server}?wsdl`, soapOptions).then(client => {
            this.client = client
        }).catch(err => {
            this.err = err
        })
    }

    request(body, method) {
        return new Promise((resolve, reject) => {
            if (this.err) {
                reject(this.err)
            } else {
                if (!this.client[method]) {
                    reject(new Error('soap client does not support this method: ${method}'))
                } else {
                    this.client[method](body, (err, result, rawResponse, soapHeader, rawRequest) => {
                        if (err) {
                            reject(err)
                        } else {
                            resolve(result)
                        }
                    })
                }
            }
        })
    }
}

module.exports = SoapClient

2.4、调试

启动start.js,

wangjinchao@IKEJCWANG-MB0 ike_httpToSoap % curl http://127.0.0.1:8080/userManager/sayHelloWorldFrom -H "Content-Type:application/json" -d '{"arg0":"ikejcwang"}'
{"return":"Hello, world, from ikejcwang"}

其余函数的请求一样,http请求报文body就是对应函数参数的json数据,响应报文就是对应函数返回值的json数据。此处是一个简单的demo,只提供了一个基本数据类型参数。

2.5、结尾

原本RPC模式隐藏掉了网络传输的细节,是很少涉及黑白名单与签名鉴权之类的操作,因为server与client必须要约定好才能发起调用,即client必须是server认可的,否则,连server长什么模样都不知道。

但有了http server这层代理,我们就可以在请求soap服务的时候基于http server上面做类似的操作了,限流策略,防刷手段……只要是http链路可以实现的都支持。

(作者对于webservice不太熟,没有实际项目使用经验,只是现今有需要针对webservice的请求做协议转换的操作,才去看了下文档后,略知一二了)

目录
相关文章
|
9月前
|
XML JSON API
识别这些API接口定义(http,https,api,RPC,webservice,Restful api ,OpenAPI)
本内容介绍了API相关的术语分类,包括传输协议(HTTP/HTTPS)、接口风格(RESTful、WebService、RPC)及开放程度(API、OpenAPI),帮助理解各类API的特点与应用场景。
|
7月前
|
缓存 负载均衡 网络协议
HTTP 与 SOCKS5 代理协议:企业级选型指南与工程化实践
面向企业网络与数据团队的代理协议选型与治理指南,基于流量特征选择HTTP或SOCKS5协议,通过多协议网关统一出站,结合托管网络降低复杂度,实现稳定吞吐、可预测时延与合规落地。
|
8月前
|
缓存 JavaScript 前端开发
对比PAC代理与传统HTTP代理的不同
总结起来,PASSIVE 提供了基础且广泛兼容解决方案而PASSIve 则提供高级灵活控制满足特殊需求但同时也带来了额外维护负担及潜再技术挑战
691 4
|
9月前
|
数据采集 负载均衡 监控
巨量http,全民ip,芝麻http,太阳http,天启代理,大麦代理,2025最新测评隧道代理选谁?
隧道代理通过云端自动切换IP,简化了传统代理的复杂操作,成为数据采集、广告监测等领域的高效工具。本文解析其工作原理,探讨选型要点,助你找到最适合的方案。
|
10月前
|
Go 定位技术
Golang中设置HTTP请求代理的策略
在实际应用中,可能还需要处理代理服务器的连接稳定性、响应时间、以及错误处理等。因此,建议在使用代理时增加适当的错误重试机制,以确保网络请求的健壮性。此外,由于网络编程涉及的细节较多,彻底测试以确认代理配置符合预期的行为也是十分重要的。
386 8
|
11月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
11月前
|
存储 缓存 前端开发
http协议调试代理工具,Fiddler免费版下载,抓包工具使用教程
Fiddler是一款功能强大的HTTP协议调试代理工具,能记录并检查电脑与互联网间的HTTP通信,支持断点设置和数据编辑。相比其他网络调试器,Fiddler操作更简单且用户友好,支持查看Cookie、HTML、JS、CSS等文件内容。它还具备HTTPS抓包、过滤设置、统计页面总重量等功能,适用于安全测试与功能测试。通过插件扩展,用户可自定义视图或分析缓存行为。支持多种HTTP请求方法(如GET、POST等)及状态码分类(1xx-5xx),是开发者调试网络请求的得力工具。同类工具有HttpWatch、Firebug、Wireshark等。
2411 1
|
数据采集 监控 安全
HTTP代理和IP代理的不同点及代理IP能带来的好处分析
总的来说,无论是HTTP代理还是IP代理,选择哪一种主要还是要看你的需求和使用场景,同时也要为可能的风险做好准备。
273 9
|
安全 网络协议 算法
HTTP/HTTPS与SOCKS5协议在隧道代理中的兼容性设计解析
本文系统探讨了构建企业级双协议隧道代理系统的挑战与实现。首先对比HTTP/HTTPS和SOCKS5协议特性,分析其在工作模型、连接管理和加密方式上的差异。接着提出兼容性架构设计,包括双协议接入层与统一隧道内核,通过协议识别模块和分层设计实现高效转换。关键技术部分深入解析协议转换引擎、连接管理策略及加密传输方案,并从性能优化、安全增强到典型应用场景全面展开。最后指出未来发展趋势将更高效、安全与智能。
597 1
|
缓存 安全 网络安全
代理协议解析:如何根据需求选择HTTP、HTTPS或SOCKS5?
本文详细介绍了HTTP、HTTPS和SOCKS5三种代理协议的特点、优缺点以及适用场景。通过对比和分析,可以根据具体需求选择最合适的代理协议。希望本文能帮助您更好地理解和应用代理协议,提高网络应用的安全性和性能。
1305 17