Flutter Dart手动生成英国National Rail OpenLDBWS SOAP信封遇SOAPAction错误
解决英国国家铁路OpenLDBWS API的SOAPAction请求头错误问题
背景
首次接触WSDL与SOAP,已用Python Zeep库成功调用英国国家铁路OpenLDBWS实时时刻表API(持有有效Token),但在Dart中手动构建SOAP信封时,服务器返回HTTP 500错误,提示无法识别SOAPAction请求头的值。
Python 成功实现代码
import json import argparse from zeep import Client import zeep.helpers from zeep.transports import Transport from requests import Session WSDL_URL = "https://realtime.nationalrail.co.uk/OpenLDBWS/wsdl.aspx?ver=2021-11-01" class NationalRailService: def __init__(self, user_token): self.user_token = user_token self.session = Session() self.transport = Transport(session=self.session) self.client = Client(WSDL_URL, transport=self.transport) self.service = self.client.service self.header = { "AccessToken": { "TokenValue": self.user_token } } def get_departure_board(self, from_crs, to_crs, num_rows=10): """ Queries the National Rail API for departure information. Args: from_crs (str): The departure station CRS code. to_crs (str): The destination station CRS code. num_rows (int, optional): The number of results to return. Defaults to 10. Returns: dict: A dictionary containing the query results, or an error message. """ try: response = self.service.GetDepartureBoard( numRows=num_rows, crs=from_crs, filterCrs=to_crs if to_crs else None, filterType="to" if to_crs else None, _soapheaders=self.header ) raw_services = ( response.trainServices.service if hasattr(response.trainServices, "service") else [] ) results = [self._remove_nulls(zeep.helpers.serialize_object(s)) for s in raw_services] return { "from": from_crs, "to": to_crs, "count": len(results), "results": results } except Exception as e: return {"error": str(e)} def _remove_nulls(self, data): """Recursively removes null values from a dictionary.""" if isinstance(data, dict): return {k: self._remove_nulls(v) for k, v in data.items() if v is not None} elif isinstance(data, list): return [self._remove_nulls(item) for item in data] else: return data def test_response(): # test method with command line args parser = argparse.ArgumentParser(description="Query National Rail API") parser.add_argument("--token", required=True, help="Your National Rail API token") parser.add_argument("--from_crs", required=True, help="Departure station CRS code") parser.add_argument("--to_crs", required=True, help="Destination station CRS code (optional)") parser.add_argument("--num_rows", type=int, default=10, help="Number of results to return (default: 10)") args = parser.parse_args() event = { "headers": { "X-API-TOKEN": args.token }, "queryStringParameters": { "from": args.from_crs, "to": args.to_crs, "numRows": args.num_rows } } response = lambda_handler(event) print(json.dumps(response, indent=2, ensure_ascii=False))
Dart 出错代码
import 'dart:developer'; import 'package:http/http.dart' as http; import 'package:xml/xml.dart' as xml; class NationalRailService { final String soapEndpoint = "https://realtime.nationalrail.co.uk/OpenLDBWS/ldb12.asmx"; final String userToken; final Map<String, String> namespaces = { 'soap': 'http://schemas.xmlsoap.org/soap/envelope/', 'ldb': 'http://thalesgroup.com/RTTI/2021-11-01/ldb/', 'typ': 'http://thalesgroup.com/RTTI/2013-11-28/Token/types' }; NationalRailService(this.userToken); Future<Map<String, dynamic>> getDepartureBoard({ required String fromCrs, String? toCrs, int numRows = 10, }) async { try { // Build SOAP envelope final envelope = _buildGetDepartureBoardEnvelope( fromCrs: fromCrs.toUpperCase(), toCrs: toCrs?.toUpperCase(), numRows: numRows, ); // Send request final response = await http.post( Uri.parse(soapEndpoint), headers: { 'Content-Type': 'text/xml; charset=utf-8', 'SOAPAction': 'http://thalesgroup.com/RTTI/2021-11-01/ldb/GetDepartureBoard' // error here - HTTP Error 500: Server did not recognize the value of HTTP Header SOAPAction }, body: envelope, ); if (response.statusCode != 200) { return { 'error': 'HTTP Error: ${response.statusCode}', 'details': response.body }; } // Parse XML response final parsedResponse = _parseGetDepartureBoardResponse(response.body); return { 'from': fromCrs, 'to': toCrs, 'count': parsedResponse['results'].length, 'results': parsedResponse['results'], }; } catch (e) { return {'error': e.toString()}; } } String _buildGetDepartureBoardEnvelope({ required String fromCrs, String? toCrs, required int numRows, }) { return ''' <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ldb="http://thalesgroup.com/RTTI/2021-11-01/ldb/" xmlns:typ="http://thalesgroup.com/RTTI/2013-11-28/Token/types"> <soap:Header> <typ:AccessToken> <typ:TokenValue>$userToken</typ:TokenValue> </typ:AccessToken> </soap:Header> <soap:Body> <ldb:GetDepartureBoard> <ldb:numRows>$numRows</ldb:numRows> <ldb:crs>$fromCrs</ldb:crs> ${toCrs != null ? '<ldb:filterCrs>$toCrs</ldb:filterCrs><ldb:filterType>to</ldb:filterType>' : ''} </ldb:GetDepartureBoard> </soap:Body> </soap:Envelope> '''; } }
问题解决方案
1. 修正SOAPAction请求头
部分SOAP服务器要求SOAPAction值用双引号包裹,将请求头修改为:
headers: { 'Content-Type': 'text/xml; charset=utf-8', 'SOAPAction': '"http://thalesgroup.com/RTTI/2021-11-01/ldb/GetDepartureBoard"' }
2. 优化SOAP信封格式
移除XML字符串中的多余缩进,避免服务器解析异常。修改后的信封构建代码:
String _buildGetDepartureBoardEnvelope({ required String fromCrs, String? toCrs, required int numRows, }) { final filterContent = toCrs != null ? '<ldb:filterCrs>$toCrs</ldb:filterCrs><ldb:filterType>to</ldb:filterType>' : ''; return ''' <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ldb="http://thalesgroup.com/RTTI/2021-11-01/ldb/" xmlns:typ="http://thalesgroup.com/RTTI/2013-11-28/Token/types"> <soap:Header> <typ:AccessToken> <typ:TokenValue>$userToken</typ:TokenValue> </typ:AccessToken> </soap:Header> <soap:Body> <ldb:GetDepartureBoard> <ldb:numRows>$numRows</ldb:numRows> <ldb:crs>$fromCrs</ldb:crs> $filterContent </ldb:GetDepartureBoard> </soap:Body> </soap:Envelope> '''; }
3. 验证命名空间与Endpoint
确认ldb命名空间末尾的斜杠完整,Endpoint地址与WSDL定义一致(当前代码中的地址正确)。
内容的提问来源于stack exchange,提问作者Taras
相关产品推荐
相关产品推荐

