This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 4c4f6fb96190 CAMEL-25941: camel-as2 - support sending through an HTTP
proxy (#28265)
4c4f6fb96190 is described below
commit 4c4f6fb9619045cc4117d306de28279824a1cd9e
Author: seshuthota <[email protected]>
AuthorDate: Sat Oct 24 19:14:10 2026 +0530
CAMEL-25941: camel-as2 - support sending through an HTTP proxy (#28265)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../org/apache/camel/catalog/components/as2.json | 40 +-
.../apache/camel/catalog/docs/as2-component.adoc | 36 ++
.../component/as2/api/AS2ClientConnection.java | 95 ++++-
...rverManagerEndpointConfigurationConfigurer.java | 28 ++
...ientManagerEndpointConfigurationConfigurer.java | 28 ++
.../component/as2/AS2ConfigurationConfigurer.java | 28 ++
.../camel/component/as2/AS2EndpointConfigurer.java | 28 ++
.../camel/component/as2/AS2EndpointUriFactory.java | 10 +-
...rverManagerEndpointConfigurationConfigurer.java | 28 ++
.../org/apache/camel/component/as2/as2.json | 40 +-
.../src/main/docs/as2-component.adoc | 36 ++
.../camel/component/as2/AS2Configuration.java | 53 +++
.../as2/internal/AS2ConnectionHelper.java | 3 +-
.../camel/component/as2/AS2ClientProxyTest.java | 452 +++++++++++++++++++++
.../apache/camel/component/as2/TestHttpProxy.java | 238 +++++++++++
.../endpoint/dsl/AS2EndpointBuilderFactory.java | 75 ++++
16 files changed, 1177 insertions(+), 41 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
index 81730d1281b5..1e5fa00800bf 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
@@ -72,24 +72,28 @@
"exchangePattern": { "index": 31, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." },
"expectContinue": { "index": 32, "kind": "parameter", "displayName":
"Expect Continue", "group": "producer", "label": "producer", "required": false,
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Controls whether the Expect: 100-Continue
header is included in outbound AS2 messages. W [...]
"lazyStartProducer": { "index": 33, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
- "accessToken": { "index": 34, "kind": "parameter", "displayName": "Access
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The access token that is used by the client
for bearer authentication." },
- "asyncMdnAllowedHosts": { "index": 35, "kind": "parameter", "displayName":
"Async Mdn Allowed Hosts", "group": "security", "label": "security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Comma separated list of host names an
asynchronous MDN may be delivered to. The delivery [...]
- "decryptingPrivateKey": { "index": 36, "kind": "parameter", "displayName":
"Decrypting Private Key", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The key used to encrypt the EDI message." },
- "encryptingAlgorithm": { "index": 37, "kind": "parameter", "displayName":
"Encrypting Algorithm", "group": "security", "label": "security", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [
"AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM",
"AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC",
"CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC",
"GOST28 [...]
- "encryptingCertificateChain": { "index": 38, "kind": "parameter",
"displayName": "Encrypting Certificate Chain", "group": "security", "label":
"security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The chain of certificates used to encrypt EDI
message." },
- "hostnameVerifier": { "index": 39, "kind": "parameter", "displayName":
"Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Set hostname verifier for SSL session." },
- "mdnAccessToken": { "index": 40, "kind": "parameter", "displayName": "Mdn
Access Token", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The access token that is used by the server
when it sends an async MDN. The optio [...]
- "mdnPassword": { "index": 41, "kind": "parameter", "displayName": "Mdn
Password", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password that is used by the server for
basic authentication when it sends an async [...]
- "mdnUserName": { "index": 42, "kind": "parameter", "displayName": "Mdn
User Name", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user-name that is used by the server for
basic authentication when it sends an asyn [...]
- "password": { "index": 43, "kind": "parameter", "displayName": "Password",
"group": "security", "label": "security", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password that is used by the client for
basic authentication." },
- "signatureVerificationRequired": { "index": 44, "kind": "parameter",
"displayName": "Signature Verification Required", "group": "security", "label":
"security", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "Whether to reject an
inbound signed AS2 message that cann [...]
- "signedReceiptMicAlgorithms": { "index": 45, "kind": "parameter",
"displayName": "Signed Receipt Mic Algorithms", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "The list of algorithms,
in order of preference, requested to generate a messa [...]
- "signingAlgorithm": { "index": 46, "kind": "parameter", "displayName":
"Signing Algorithm", "group": "security", "label": "security", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [
"SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA",
"MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA",
"SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA",
"RIPEMD256 [...]
- "signingCertificateChain": { "index": 47, "kind": "parameter",
"displayName": "Signing Certificate Chain", "group": "security", "label":
"security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The chain of certificates used to sign EDI
message." },
- "signingPrivateKey": { "index": 48, "kind": "parameter", "displayName":
"Signing Private Key", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The key used to sign the EDI message." },
- "sslContext": { "index": 49, "kind": "parameter", "displayName": "Ssl
Context", "group": "security", "label": "security", "required": false, "type":
"object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Set SSL context for connection to remote
server." },
- "userName": { "index": 50, "kind": "parameter", "displayName": "User
Name", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user-name that is used by the client for
basic authentication. If options for basic authen [...]
- "validateSigningCertificateChain": { "index": 51, "kind": "parameter",
"displayName": "Validate Signing Certificate Chain", "group": "security",
"label": "security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Certificates to validate the message's
signature ag [...]
+ "proxyAuthPassword": { "index": 34, "kind": "parameter", "displayName":
"Proxy Auth Password", "group": "proxy", "label": "producer,proxy", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password for basic authentication with the
HTTP proxy." },
+ "proxyAuthUsername": { "index": 35, "kind": "parameter", "displayName":
"Proxy Auth Username", "group": "proxy", "label": "producer,proxy", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user name for basic authentication with
the HTTP proxy. The credenti [...]
+ "proxyHost": { "index": 36, "kind": "parameter", "displayName": "Proxy
Host", "group": "proxy", "label": "producer,proxy", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The host name of an HTTP proxy the client
sends its AS2 messages through. The connection to the proxy is plain [...]
+ "proxyPort": { "index": 37, "kind": "parameter", "displayName": "Proxy
Port", "group": "proxy", "label": "producer,proxy", "required": false, "type":
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The port number of the HTTP proxy." },
+ "accessToken": { "index": 38, "kind": "parameter", "displayName": "Access
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The access token that is used by the client
for bearer authentication." },
+ "asyncMdnAllowedHosts": { "index": 39, "kind": "parameter", "displayName":
"Async Mdn Allowed Hosts", "group": "security", "label": "security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Comma separated list of host names an
asynchronous MDN may be delivered to. The delivery [...]
+ "decryptingPrivateKey": { "index": 40, "kind": "parameter", "displayName":
"Decrypting Private Key", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The key used to encrypt the EDI message." },
+ "encryptingAlgorithm": { "index": 41, "kind": "parameter", "displayName":
"Encrypting Algorithm", "group": "security", "label": "security", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [
"AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM",
"AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC",
"CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC",
"GOST28 [...]
+ "encryptingCertificateChain": { "index": 42, "kind": "parameter",
"displayName": "Encrypting Certificate Chain", "group": "security", "label":
"security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The chain of certificates used to encrypt EDI
message." },
+ "hostnameVerifier": { "index": 43, "kind": "parameter", "displayName":
"Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Set hostname verifier for SSL session." },
+ "mdnAccessToken": { "index": 44, "kind": "parameter", "displayName": "Mdn
Access Token", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The access token that is used by the server
when it sends an async MDN. The optio [...]
+ "mdnPassword": { "index": 45, "kind": "parameter", "displayName": "Mdn
Password", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password that is used by the server for
basic authentication when it sends an async [...]
+ "mdnUserName": { "index": 46, "kind": "parameter", "displayName": "Mdn
User Name", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user-name that is used by the server for
basic authentication when it sends an asyn [...]
+ "password": { "index": 47, "kind": "parameter", "displayName": "Password",
"group": "security", "label": "security", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password that is used by the client for
basic authentication." },
+ "signatureVerificationRequired": { "index": 48, "kind": "parameter",
"displayName": "Signature Verification Required", "group": "security", "label":
"security", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "Whether to reject an
inbound signed AS2 message that cann [...]
+ "signedReceiptMicAlgorithms": { "index": 49, "kind": "parameter",
"displayName": "Signed Receipt Mic Algorithms", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "The list of algorithms,
in order of preference, requested to generate a messa [...]
+ "signingAlgorithm": { "index": 50, "kind": "parameter", "displayName":
"Signing Algorithm", "group": "security", "label": "security", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [
"SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA",
"MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA",
"SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA",
"RIPEMD256 [...]
+ "signingCertificateChain": { "index": 51, "kind": "parameter",
"displayName": "Signing Certificate Chain", "group": "security", "label":
"security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The chain of certificates used to sign EDI
message." },
+ "signingPrivateKey": { "index": 52, "kind": "parameter", "displayName":
"Signing Private Key", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The key used to sign the EDI message." },
+ "sslContext": { "index": 53, "kind": "parameter", "displayName": "Ssl
Context", "group": "security", "label": "security", "required": false, "type":
"object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Set SSL context for connection to remote
server." },
+ "userName": { "index": 54, "kind": "parameter", "displayName": "User
Name", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user-name that is used by the client for
basic authentication. If options for basic authen [...]
+ "validateSigningCertificateChain": { "index": 55, "kind": "parameter",
"displayName": "Validate Signing Certificate Chain", "group": "security",
"label": "security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Certificates to validate the message's
signature ag [...]
},
"apis": {
"client": { "consumerOnly": false, "producerOnly": true, "description":
"Sends EDI Messages over HTTP", "methods": { "send": { "description": "Send
ediMessage to trading partner", "signatures": [
"org.apache.hc.core5.http.protocol.HttpCoreContext send(Object ediMessage,
String requestUri, String subject, String from, String as2From, String as2To,
org.apache.camel.component.as2.api.AS2MessageStructure as2MessageStructure,
String ediMessageContentType, String ediMessageCharset, String [...]
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc
index 8b3988308f01..b437954ec5e2 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc
@@ -107,3 +107,39 @@ consumer applies its own keys and certificates and its own
asynchronous MDN sett
The settings of the shared server connection still come from the first
endpoint created on the port:
`sslContextParameters` and `sslContext` (used for the TLS listener and for the
https delivery of asynchronous MDNs),
`mdnMessageTemplate`, `server`, `serverFqdn` and `as2Version`. They are per
port, not per consumer.
+
+=== Sending through an HTTP proxy
+
+When outbound traffic must go through an HTTP forward proxy, set `proxyHost`
and `proxyPort` on the
+`as2://client/send` endpoint. The client connects to the proxy over plain http:
+
+* without an `sslContext`, the AS2 message is sent to the proxy with an
absolute request URI;
+* with an `sslContext`, the client asks the proxy to open a `CONNECT` tunnel
to `targetHostname` and
+`targetPortNumber`, and the TLS handshake and hostname verification are made
with the target through the tunnel.
+
+If the proxy requires authentication, set `proxyAuthUsername` and
`proxyAuthPassword`. They are sent to the
+proxy as a preemptive Basic `Proxy-Authorization` header, on the `CONNECT`
request when tunnelling, and are not
+sent to the AS2 partner. Setting only one of them is a configuration error:
the endpoint fails to start. If the
+proxy refuses the request, for example with `407 Proxy Authentication
Required`, the send fails with an exception,
+as the message has not reached the partner. The proxy options apply to the AS2
client only: asynchronous MDNs are
+delivered directly.
+
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:sendToPartner
+ steps:
+ - to:
+ uri: as2://client/send
+ parameters:
+ inBody: ediMessage
+ targetHostname: partner.example.com
+ targetPortNumber: 443
+ sslContext: "#partnerSslContext"
+ proxyHost: proxy.internal
+ proxyPort: 3128
+ proxyAuthUsername: "{{proxy.user}}"
+ proxyAuthPassword: "{{proxy.password}}"
+ # ... the AS2 message options
+----
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
index dafaa6c63d2b..570ffc7561e1 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
@@ -18,7 +18,9 @@ package org.apache.camel.component.as2.api;
import java.io.IOException;
import java.net.Socket;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
+import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@@ -50,7 +52,10 @@ import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.SocketConfig;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.http.message.MessageSupport;
+import org.apache.hc.core5.http.message.StatusLine;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.protocol.HttpCoreContext;
import org.apache.hc.core5.http.protocol.HttpProcessor;
@@ -61,6 +66,7 @@ import org.apache.hc.core5.http.protocol.RequestDate;
import org.apache.hc.core5.http.protocol.RequestExpectContinue;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent;
+import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.net.URIAuthority;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
@@ -70,6 +76,8 @@ public class AS2ClientConnection {
private static final int RETRIEVE_CONNECTION_TIMEOUT_SECONDS = 5;
private HttpHost targetHost;
+ private HttpRoute route;
+ private String proxyAuthorization;
private HttpProcessor httpProcessor;
private String as2Version;
private String userAgent;
@@ -91,6 +99,16 @@ public class AS2ClientConnection {
Integer connectionPoolMaxSize, Duration
connectionPoolTtl,
SSLContext sslContext, HostnameVerifier
hostnameVerifier,
boolean expectContinue) throws IOException {
+ this(as2Version, userAgent, clientFqdn, targetHostName,
targetPortNumber, socketTimeout, connectionTimeout,
+ connectionPoolMaxSize, connectionPoolTtl, sslContext,
hostnameVerifier, expectContinue, null, null, null, null);
+ }
+
+ public AS2ClientConnection(String as2Version, String userAgent, String
clientFqdn, String targetHostName,
+ Integer targetPortNumber, Duration
socketTimeout, Duration connectionTimeout,
+ Integer connectionPoolMaxSize, Duration
connectionPoolTtl,
+ SSLContext sslContext, HostnameVerifier
hostnameVerifier,
+ boolean expectContinue, String proxyHost,
Integer proxyPort,
+ String proxyAuthUsername, String
proxyAuthPassword) throws IOException {
this.as2Version = ObjectHelper.notNull(as2Version, "as2Version");
this.userAgent = ObjectHelper.notNull(userAgent, "userAgent");
@@ -99,6 +117,21 @@ public class AS2ClientConnection {
sslContext != null ? "https" : "http",
ObjectHelper.notNull(targetHostName, "targetHostName"),
ObjectHelper.notNull(targetPortNumber, "targetPortNumber"));
+ if (proxyHost != null) {
+ // the connection to the proxy itself is plain http, whatever the
target scheme: an https target is
+ // reached through a CONNECT tunnel
+ HttpHost proxy = new HttpHost("http", proxyHost,
ObjectHelper.notNull(proxyPort, "proxyPort"));
+ this.route = new HttpRoute(targetHost, null, proxy, sslContext !=
null);
+ if (proxyAuthUsername != null && proxyAuthPassword != null) {
+ this.proxyAuthorization = "Basic " +
Base64.getEncoder().encodeToString(
+ (proxyAuthUsername + ":" +
proxyAuthPassword).getBytes(StandardCharsets.UTF_8));
+ } else if (proxyAuthUsername != null || proxyAuthPassword != null)
{
+ // one credential alone is a configuration mistake: report it
now rather than as a 407 on the first send
+ throw new IllegalArgumentException("proxyAuthUsername and
proxyAuthPassword must be set together");
+ }
+ } else {
+ this.route = new HttpRoute(targetHost);
+ }
ObjectHelper.notNull(socketTimeout, "socketTimeout");
this.connectionTimeoutMilliseconds = (int)
ObjectHelper.notNull(connectionTimeout, "connectionTimeout").toMillis();
ObjectHelper.notNull(connectionPoolMaxSize, "connectionPoolMaxSize");
@@ -191,8 +224,17 @@ public class AS2ClientConnection {
public HttpResponse send(ClassicHttpRequest request, HttpCoreContext
httpContext)
throws HttpException, IOException, InterruptedException,
ExecutionException, TimeoutException {
- HttpRoute route = new HttpRoute(targetHost);
request.setAuthority(new URIAuthority(targetHost));
+ if (route.getProxyHost() != null && !route.isTunnelled()) {
+ // a proxy forwarding plain http needs the absolute request-URI,
and its credentials on every request
+ request.setScheme(targetHost.getSchemeName());
+ if (request instanceof BasicHttpRequest basicRequest) {
+ basicRequest.setAbsoluteRequestUri(true);
+ }
+ if (proxyAuthorization != null) {
+ request.setHeader(HttpHeaders.PROXY_AUTHORIZATION,
proxyAuthorization);
+ }
+ }
LeaseRequest leaseRequest =
connectionPoolManager.lease(UUID.randomUUID().toString(), route, null);
ConnectionEndpoint endpoint = null;
@@ -200,7 +242,18 @@ public class AS2ClientConnection {
try {
endpoint =
leaseRequest.get(Timeout.ofSeconds(RETRIEVE_CONNECTION_TIMEOUT_SECONDS));
if (!endpoint.isConnected()) {
- connectionPoolManager.connect(endpoint,
TimeValue.ofMilliseconds(connectionTimeoutMilliseconds), httpContext);
+ try {
+ connectionPoolManager.connect(endpoint,
TimeValue.ofMilliseconds(connectionTimeoutMilliseconds),
+ httpContext);
+ if (route.isTunnelled()) {
+ createTunnel(endpoint);
+ connectionPoolManager.upgrade(endpoint, httpContext);
+ }
+ } catch (IOException | HttpException | RuntimeException e) {
+ // a connection without its tunnel or its TLS layer must
not go back to the pool
+ endpoint.close(CloseMode.IMMEDIATE);
+ throw e;
+ }
}
// Execute Request
ConnectionEndpoint.RequestExecutor requestExecutor = new
ConnectionEndpoint.RequestExecutor() {
@@ -215,6 +268,15 @@ public class AS2ClientConnection {
}
};
response = endpoint.execute(UUID.randomUUID().toString(), request,
requestExecutor, httpContext);
+ if (route.getProxyHost() != null && !route.isTunnelled()
+ && response.getCode() ==
HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
+ // the proxy may keep the connection open with the body of its
407 unread: it must not be reused
+ endpoint.close(CloseMode.IMMEDIATE);
+ // only the proxy answers 407: the message has not reached the
AS2 partner
+ throw new HttpException(
+ "Proxy " + route.getProxyHost().toHostString() + "
refused to forward the request to "
+ + targetHost.toHostString() + ": " +
new StatusLine(response));
+ }
} finally {
if (endpoint != null) {
TimeValue keepAlive = null;
@@ -239,9 +301,38 @@ public class AS2ClientConnection {
return response;
}
+ /**
+ * Asks the proxy to open a tunnel to the target. The proxy credentials go
on the CONNECT request only, so they are
+ * never seen by the AS2 partner at the other end of the tunnel.
+ */
+ private void createTunnel(ConnectionEndpoint endpoint) throws IOException,
HttpException {
+ ClassicHttpRequest connect = new
BasicClassicHttpRequest(Method.CONNECT, targetHost, targetHost.toHostString());
+ connect.setVersion(HttpVersion.HTTP_1_1);
+ connect.setHeader(HttpHeaders.HOST, targetHost.toHostString());
+ connect.setHeader(HttpHeaders.USER_AGENT, userAgent);
+ if (proxyAuthorization != null) {
+ connect.setHeader(HttpHeaders.PROXY_AUTHORIZATION,
proxyAuthorization);
+ }
+ // a separate context, so the caller's context keeps the AS2 request
and response
+ ClassicHttpResponse response =
endpoint.execute(UUID.randomUUID().toString(), connect,
+ (request, conn, context) -> new
HttpRequestExecutor().execute(request, conn, context),
+ HttpCoreContext.create());
+ int status = response.getCode();
+ if (status < HttpStatus.SC_SUCCESS || status >=
HttpStatus.SC_REDIRECTION) {
+ throw new HttpException(
+ "Proxy " + route.getProxyHost().toHostString() + " refused
to open a tunnel to "
+ + targetHost.toHostString() + ": " + new
StatusLine(response));
+ }
+ }
+
private HttpConnection createTestConnection(
SSLContext sslContext,
HttpConnectionFactory<ManagedHttpClientConnection> connFactory)
throws IOException {
+ HttpHost proxy = route.getProxyHost();
+ if (proxy != null) {
+ // the target is only reached through the proxy once a request is
sent, so check the proxy can be reached
+ return connFactory.createConnection(new
Socket(proxy.getHostName(), proxy.getPort()));
+ }
if (sslContext == null) {
return connFactory.createConnection(new
Socket(targetHost.getHostName(), targetHost.getPort()));
} else {
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
index beb0ce63526f..2ec8edb4920e 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
@@ -53,6 +53,10 @@ public class
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
map.put("MdnUserName", java.lang.String.class);
map.put("MethodName", java.lang.String.class);
map.put("Password", java.lang.String.class);
+ map.put("ProxyAuthPassword", java.lang.String.class);
+ map.put("ProxyAuthUsername", java.lang.String.class);
+ map.put("ProxyHost", java.lang.String.class);
+ map.put("ProxyPort", java.lang.Integer.class);
map.put("ReceiptDeliveryOption", java.lang.String.class);
map.put("RequestUri", java.lang.String.class);
map.put("RequestUriPattern", java.lang.String.class);
@@ -138,6 +142,14 @@ public class
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
case "methodname":
case "methodName": target.setMethodName(property(camelContext,
java.lang.String.class, value)); return true;
case "password": target.setPassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyauthpassword":
+ case "proxyAuthPassword":
target.setProxyAuthPassword(property(camelContext, java.lang.String.class,
value)); return true;
+ case "proxyauthusername":
+ case "proxyAuthUsername":
target.setProxyAuthUsername(property(camelContext, java.lang.String.class,
value)); return true;
+ case "proxyhost":
+ case "proxyHost": target.setProxyHost(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyport":
+ case "proxyPort": target.setProxyPort(property(camelContext,
java.lang.Integer.class, value)); return true;
case "receiptdeliveryoption":
case "receiptDeliveryOption":
target.setReceiptDeliveryOption(property(camelContext, java.lang.String.class,
value)); return true;
case "requesturi":
@@ -244,6 +256,14 @@ public class
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
case "methodname":
case "methodName": return java.lang.String.class;
case "password": return java.lang.String.class;
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return java.lang.String.class;
+ case "proxyauthusername":
+ case "proxyAuthUsername": return java.lang.String.class;
+ case "proxyhost":
+ case "proxyHost": return java.lang.String.class;
+ case "proxyport":
+ case "proxyPort": return java.lang.Integer.class;
case "receiptdeliveryoption":
case "receiptDeliveryOption": return java.lang.String.class;
case "requesturi":
@@ -346,6 +366,14 @@ public class
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
case "methodname":
case "methodName": return target.getMethodName();
case "password": return target.getPassword();
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return target.getProxyAuthPassword();
+ case "proxyauthusername":
+ case "proxyAuthUsername": return target.getProxyAuthUsername();
+ case "proxyhost":
+ case "proxyHost": return target.getProxyHost();
+ case "proxyport":
+ case "proxyPort": return target.getProxyPort();
case "receiptdeliveryoption":
case "receiptDeliveryOption": return target.getReceiptDeliveryOption();
case "requesturi":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
index 96a5158d8e56..395cb3eca76d 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
@@ -55,6 +55,10 @@ public class AS2ClientManagerEndpointConfigurationConfigurer
extends org.apache.
map.put("MdnUserName", java.lang.String.class);
map.put("MethodName", java.lang.String.class);
map.put("Password", java.lang.String.class);
+ map.put("ProxyAuthPassword", java.lang.String.class);
+ map.put("ProxyAuthUsername", java.lang.String.class);
+ map.put("ProxyHost", java.lang.String.class);
+ map.put("ProxyPort", java.lang.Integer.class);
map.put("ReceiptDeliveryOption", java.lang.String.class);
map.put("RequestUri", java.lang.String.class);
map.put("Server", java.lang.String.class);
@@ -143,6 +147,14 @@ public class
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
case "methodname":
case "methodName": target.setMethodName(property(camelContext,
java.lang.String.class, value)); return true;
case "password": target.setPassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyauthpassword":
+ case "proxyAuthPassword":
target.setProxyAuthPassword(property(camelContext, java.lang.String.class,
value)); return true;
+ case "proxyauthusername":
+ case "proxyAuthUsername":
target.setProxyAuthUsername(property(camelContext, java.lang.String.class,
value)); return true;
+ case "proxyhost":
+ case "proxyHost": target.setProxyHost(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyport":
+ case "proxyPort": target.setProxyPort(property(camelContext,
java.lang.Integer.class, value)); return true;
case "receiptdeliveryoption":
case "receiptDeliveryOption":
target.setReceiptDeliveryOption(property(camelContext, java.lang.String.class,
value)); return true;
case "requesturi":
@@ -251,6 +263,14 @@ public class
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
case "methodname":
case "methodName": return java.lang.String.class;
case "password": return java.lang.String.class;
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return java.lang.String.class;
+ case "proxyauthusername":
+ case "proxyAuthUsername": return java.lang.String.class;
+ case "proxyhost":
+ case "proxyHost": return java.lang.String.class;
+ case "proxyport":
+ case "proxyPort": return java.lang.Integer.class;
case "receiptdeliveryoption":
case "receiptDeliveryOption": return java.lang.String.class;
case "requesturi":
@@ -355,6 +375,14 @@ public class
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
case "methodname":
case "methodName": return target.getMethodName();
case "password": return target.getPassword();
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return target.getProxyAuthPassword();
+ case "proxyauthusername":
+ case "proxyAuthUsername": return target.getProxyAuthUsername();
+ case "proxyhost":
+ case "proxyHost": return target.getProxyHost();
+ case "proxyport":
+ case "proxyPort": return target.getProxyPort();
case "receiptdeliveryoption":
case "receiptDeliveryOption": return target.getReceiptDeliveryOption();
case "requesturi":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
index 0af682c05337..135bbbe25b06 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
@@ -53,6 +53,10 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
map.put("MdnUserName", java.lang.String.class);
map.put("MethodName", java.lang.String.class);
map.put("Password", java.lang.String.class);
+ map.put("ProxyAuthPassword", java.lang.String.class);
+ map.put("ProxyAuthUsername", java.lang.String.class);
+ map.put("ProxyHost", java.lang.String.class);
+ map.put("ProxyPort", java.lang.Integer.class);
map.put("ReceiptDeliveryOption", java.lang.String.class);
map.put("RequestUri", java.lang.String.class);
map.put("Server", java.lang.String.class);
@@ -137,6 +141,14 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "methodname":
case "methodName": target.setMethodName(property(camelContext,
java.lang.String.class, value)); return true;
case "password": target.setPassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyauthpassword":
+ case "proxyAuthPassword":
target.setProxyAuthPassword(property(camelContext, java.lang.String.class,
value)); return true;
+ case "proxyauthusername":
+ case "proxyAuthUsername":
target.setProxyAuthUsername(property(camelContext, java.lang.String.class,
value)); return true;
+ case "proxyhost":
+ case "proxyHost": target.setProxyHost(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyport":
+ case "proxyPort": target.setProxyPort(property(camelContext,
java.lang.Integer.class, value)); return true;
case "receiptdeliveryoption":
case "receiptDeliveryOption":
target.setReceiptDeliveryOption(property(camelContext, java.lang.String.class,
value)); return true;
case "requesturi":
@@ -241,6 +253,14 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "methodname":
case "methodName": return java.lang.String.class;
case "password": return java.lang.String.class;
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return java.lang.String.class;
+ case "proxyauthusername":
+ case "proxyAuthUsername": return java.lang.String.class;
+ case "proxyhost":
+ case "proxyHost": return java.lang.String.class;
+ case "proxyport":
+ case "proxyPort": return java.lang.Integer.class;
case "receiptdeliveryoption":
case "receiptDeliveryOption": return java.lang.String.class;
case "requesturi":
@@ -341,6 +361,14 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "methodname":
case "methodName": return target.getMethodName();
case "password": return target.getPassword();
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return target.getProxyAuthPassword();
+ case "proxyauthusername":
+ case "proxyAuthUsername": return target.getProxyAuthUsername();
+ case "proxyhost":
+ case "proxyHost": return target.getProxyHost();
+ case "proxyport":
+ case "proxyPort": return target.getProxyPort();
case "receiptdeliveryoption":
case "receiptDeliveryOption": return target.getReceiptDeliveryOption();
case "requesturi":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
index 69a06454c0db..50f3fc057ef5 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
@@ -56,6 +56,10 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
map.put("ExchangePattern", org.apache.camel.ExchangePattern.class);
map.put("ExpectContinue", boolean.class);
map.put("LazyStartProducer", boolean.class);
+ map.put("ProxyAuthPassword", java.lang.String.class);
+ map.put("ProxyAuthUsername", java.lang.String.class);
+ map.put("ProxyHost", java.lang.String.class);
+ map.put("ProxyPort", java.lang.Integer.class);
map.put("AccessToken", java.lang.String.class);
map.put("AsyncMdnAllowedHosts", java.lang.String.class);
map.put("DecryptingPrivateKey", java.security.PrivateKey.class);
@@ -145,6 +149,14 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "mdnusername":
case "mdnUserName":
target.getConfiguration().setMdnUserName(property(camelContext,
java.lang.String.class, value)); return true;
case "password":
target.getConfiguration().setPassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyauthpassword":
+ case "proxyAuthPassword":
target.getConfiguration().setProxyAuthPassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyauthusername":
+ case "proxyAuthUsername":
target.getConfiguration().setProxyAuthUsername(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyhost":
+ case "proxyHost":
target.getConfiguration().setProxyHost(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyport":
+ case "proxyPort":
target.getConfiguration().setProxyPort(property(camelContext,
java.lang.Integer.class, value)); return true;
case "receiptdeliveryoption":
case "receiptDeliveryOption":
target.getConfiguration().setReceiptDeliveryOption(property(camelContext,
java.lang.String.class, value)); return true;
case "requesturi":
@@ -253,6 +265,14 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "mdnusername":
case "mdnUserName": return java.lang.String.class;
case "password": return java.lang.String.class;
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return java.lang.String.class;
+ case "proxyauthusername":
+ case "proxyAuthUsername": return java.lang.String.class;
+ case "proxyhost":
+ case "proxyHost": return java.lang.String.class;
+ case "proxyport":
+ case "proxyPort": return java.lang.Integer.class;
case "receiptdeliveryoption":
case "receiptDeliveryOption": return java.lang.String.class;
case "requesturi":
@@ -357,6 +377,14 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "mdnusername":
case "mdnUserName": return target.getConfiguration().getMdnUserName();
case "password": return target.getConfiguration().getPassword();
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return
target.getConfiguration().getProxyAuthPassword();
+ case "proxyauthusername":
+ case "proxyAuthUsername": return
target.getConfiguration().getProxyAuthUsername();
+ case "proxyhost":
+ case "proxyHost": return target.getConfiguration().getProxyHost();
+ case "proxyport":
+ case "proxyPort": return target.getConfiguration().getProxyPort();
case "receiptdeliveryoption":
case "receiptDeliveryOption": return
target.getConfiguration().getReceiptDeliveryOption();
case "requesturi":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
index 08910fb590dc..bbf65d808ee4 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
@@ -24,7 +24,7 @@ public class AS2EndpointUriFactory extends
org.apache.camel.support.component.En
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(55);
+ Set<String> props = new HashSet<>(59);
props.add("accessToken");
props.add("apiName");
props.add("as2From");
@@ -62,6 +62,10 @@ public class AS2EndpointUriFactory extends
org.apache.camel.support.component.En
props.add("mdnUserName");
props.add("methodName");
props.add("password");
+ props.add("proxyAuthPassword");
+ props.add("proxyAuthUsername");
+ props.add("proxyHost");
+ props.add("proxyPort");
props.add("receiptDeliveryOption");
props.add("requestUri");
props.add("requestUriPattern");
@@ -81,12 +85,14 @@ public class AS2EndpointUriFactory extends
org.apache.camel.support.component.En
props.add("userName");
props.add("validateSigningCertificateChain");
PROPERTY_NAMES = Collections.unmodifiableSet(props);
- Set<String> secretProps = new HashSet<>(6);
+ Set<String> secretProps = new HashSet<>(8);
secretProps.add("accessToken");
secretProps.add("mdnAccessToken");
secretProps.add("mdnPassword");
secretProps.add("mdnUserName");
secretProps.add("password");
+ secretProps.add("proxyAuthPassword");
+ secretProps.add("proxyAuthUsername");
secretProps.add("userName");
SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps);
ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet();
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
index a3580c9594e2..d8a55e239a3b 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
@@ -53,6 +53,10 @@ public class AS2ServerManagerEndpointConfigurationConfigurer
extends org.apache.
map.put("MdnUserName", java.lang.String.class);
map.put("MethodName", java.lang.String.class);
map.put("Password", java.lang.String.class);
+ map.put("ProxyAuthPassword", java.lang.String.class);
+ map.put("ProxyAuthUsername", java.lang.String.class);
+ map.put("ProxyHost", java.lang.String.class);
+ map.put("ProxyPort", java.lang.Integer.class);
map.put("ReceiptDeliveryOption", java.lang.String.class);
map.put("RequestUri", java.lang.String.class);
map.put("RequestUriPattern", java.lang.String.class);
@@ -138,6 +142,14 @@ public class
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
case "methodname":
case "methodName": target.setMethodName(property(camelContext,
java.lang.String.class, value)); return true;
case "password": target.setPassword(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyauthpassword":
+ case "proxyAuthPassword":
target.setProxyAuthPassword(property(camelContext, java.lang.String.class,
value)); return true;
+ case "proxyauthusername":
+ case "proxyAuthUsername":
target.setProxyAuthUsername(property(camelContext, java.lang.String.class,
value)); return true;
+ case "proxyhost":
+ case "proxyHost": target.setProxyHost(property(camelContext,
java.lang.String.class, value)); return true;
+ case "proxyport":
+ case "proxyPort": target.setProxyPort(property(camelContext,
java.lang.Integer.class, value)); return true;
case "receiptdeliveryoption":
case "receiptDeliveryOption":
target.setReceiptDeliveryOption(property(camelContext, java.lang.String.class,
value)); return true;
case "requesturi":
@@ -244,6 +256,14 @@ public class
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
case "methodname":
case "methodName": return java.lang.String.class;
case "password": return java.lang.String.class;
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return java.lang.String.class;
+ case "proxyauthusername":
+ case "proxyAuthUsername": return java.lang.String.class;
+ case "proxyhost":
+ case "proxyHost": return java.lang.String.class;
+ case "proxyport":
+ case "proxyPort": return java.lang.Integer.class;
case "receiptdeliveryoption":
case "receiptDeliveryOption": return java.lang.String.class;
case "requesturi":
@@ -346,6 +366,14 @@ public class
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
case "methodname":
case "methodName": return target.getMethodName();
case "password": return target.getPassword();
+ case "proxyauthpassword":
+ case "proxyAuthPassword": return target.getProxyAuthPassword();
+ case "proxyauthusername":
+ case "proxyAuthUsername": return target.getProxyAuthUsername();
+ case "proxyhost":
+ case "proxyHost": return target.getProxyHost();
+ case "proxyport":
+ case "proxyPort": return target.getProxyPort();
case "receiptdeliveryoption":
case "receiptDeliveryOption": return target.getReceiptDeliveryOption();
case "requesturi":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
b/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
index 81730d1281b5..1e5fa00800bf 100644
---
a/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
+++
b/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
@@ -72,24 +72,28 @@
"exchangePattern": { "index": 31, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." },
"expectContinue": { "index": 32, "kind": "parameter", "displayName":
"Expect Continue", "group": "producer", "label": "producer", "required": false,
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Controls whether the Expect: 100-Continue
header is included in outbound AS2 messages. W [...]
"lazyStartProducer": { "index": 33, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
- "accessToken": { "index": 34, "kind": "parameter", "displayName": "Access
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The access token that is used by the client
for bearer authentication." },
- "asyncMdnAllowedHosts": { "index": 35, "kind": "parameter", "displayName":
"Async Mdn Allowed Hosts", "group": "security", "label": "security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Comma separated list of host names an
asynchronous MDN may be delivered to. The delivery [...]
- "decryptingPrivateKey": { "index": 36, "kind": "parameter", "displayName":
"Decrypting Private Key", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The key used to encrypt the EDI message." },
- "encryptingAlgorithm": { "index": 37, "kind": "parameter", "displayName":
"Encrypting Algorithm", "group": "security", "label": "security", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [
"AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM",
"AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC",
"CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC",
"GOST28 [...]
- "encryptingCertificateChain": { "index": 38, "kind": "parameter",
"displayName": "Encrypting Certificate Chain", "group": "security", "label":
"security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The chain of certificates used to encrypt EDI
message." },
- "hostnameVerifier": { "index": 39, "kind": "parameter", "displayName":
"Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Set hostname verifier for SSL session." },
- "mdnAccessToken": { "index": 40, "kind": "parameter", "displayName": "Mdn
Access Token", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The access token that is used by the server
when it sends an async MDN. The optio [...]
- "mdnPassword": { "index": 41, "kind": "parameter", "displayName": "Mdn
Password", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password that is used by the server for
basic authentication when it sends an async [...]
- "mdnUserName": { "index": 42, "kind": "parameter", "displayName": "Mdn
User Name", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user-name that is used by the server for
basic authentication when it sends an asyn [...]
- "password": { "index": 43, "kind": "parameter", "displayName": "Password",
"group": "security", "label": "security", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password that is used by the client for
basic authentication." },
- "signatureVerificationRequired": { "index": 44, "kind": "parameter",
"displayName": "Signature Verification Required", "group": "security", "label":
"security", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "Whether to reject an
inbound signed AS2 message that cann [...]
- "signedReceiptMicAlgorithms": { "index": 45, "kind": "parameter",
"displayName": "Signed Receipt Mic Algorithms", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "The list of algorithms,
in order of preference, requested to generate a messa [...]
- "signingAlgorithm": { "index": 46, "kind": "parameter", "displayName":
"Signing Algorithm", "group": "security", "label": "security", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [
"SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA",
"MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA",
"SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA",
"RIPEMD256 [...]
- "signingCertificateChain": { "index": 47, "kind": "parameter",
"displayName": "Signing Certificate Chain", "group": "security", "label":
"security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The chain of certificates used to sign EDI
message." },
- "signingPrivateKey": { "index": 48, "kind": "parameter", "displayName":
"Signing Private Key", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The key used to sign the EDI message." },
- "sslContext": { "index": 49, "kind": "parameter", "displayName": "Ssl
Context", "group": "security", "label": "security", "required": false, "type":
"object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Set SSL context for connection to remote
server." },
- "userName": { "index": 50, "kind": "parameter", "displayName": "User
Name", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user-name that is used by the client for
basic authentication. If options for basic authen [...]
- "validateSigningCertificateChain": { "index": 51, "kind": "parameter",
"displayName": "Validate Signing Certificate Chain", "group": "security",
"label": "security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Certificates to validate the message's
signature ag [...]
+ "proxyAuthPassword": { "index": 34, "kind": "parameter", "displayName":
"Proxy Auth Password", "group": "proxy", "label": "producer,proxy", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password for basic authentication with the
HTTP proxy." },
+ "proxyAuthUsername": { "index": 35, "kind": "parameter", "displayName":
"Proxy Auth Username", "group": "proxy", "label": "producer,proxy", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user name for basic authentication with
the HTTP proxy. The credenti [...]
+ "proxyHost": { "index": 36, "kind": "parameter", "displayName": "Proxy
Host", "group": "proxy", "label": "producer,proxy", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The host name of an HTTP proxy the client
sends its AS2 messages through. The connection to the proxy is plain [...]
+ "proxyPort": { "index": 37, "kind": "parameter", "displayName": "Proxy
Port", "group": "proxy", "label": "producer,proxy", "required": false, "type":
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The port number of the HTTP proxy." },
+ "accessToken": { "index": 38, "kind": "parameter", "displayName": "Access
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The access token that is used by the client
for bearer authentication." },
+ "asyncMdnAllowedHosts": { "index": 39, "kind": "parameter", "displayName":
"Async Mdn Allowed Hosts", "group": "security", "label": "security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Comma separated list of host names an
asynchronous MDN may be delivered to. The delivery [...]
+ "decryptingPrivateKey": { "index": 40, "kind": "parameter", "displayName":
"Decrypting Private Key", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The key used to encrypt the EDI message." },
+ "encryptingAlgorithm": { "index": 41, "kind": "parameter", "displayName":
"Encrypting Algorithm", "group": "security", "label": "security", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [
"AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM",
"AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC",
"CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC",
"GOST28 [...]
+ "encryptingCertificateChain": { "index": 42, "kind": "parameter",
"displayName": "Encrypting Certificate Chain", "group": "security", "label":
"security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The chain of certificates used to encrypt EDI
message." },
+ "hostnameVerifier": { "index": 43, "kind": "parameter", "displayName":
"Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Set hostname verifier for SSL session." },
+ "mdnAccessToken": { "index": 44, "kind": "parameter", "displayName": "Mdn
Access Token", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The access token that is used by the server
when it sends an async MDN. The optio [...]
+ "mdnPassword": { "index": 45, "kind": "parameter", "displayName": "Mdn
Password", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password that is used by the server for
basic authentication when it sends an async [...]
+ "mdnUserName": { "index": 46, "kind": "parameter", "displayName": "Mdn
User Name", "group": "security", "label": "security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user-name that is used by the server for
basic authentication when it sends an asyn [...]
+ "password": { "index": 47, "kind": "parameter", "displayName": "Password",
"group": "security", "label": "security", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The password that is used by the client for
basic authentication." },
+ "signatureVerificationRequired": { "index": 48, "kind": "parameter",
"displayName": "Signature Verification Required", "group": "security", "label":
"security", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "Whether to reject an
inbound signed AS2 message that cann [...]
+ "signedReceiptMicAlgorithms": { "index": 49, "kind": "parameter",
"displayName": "Signed Receipt Mic Algorithms", "group": "security", "label":
"security", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "The list of algorithms,
in order of preference, requested to generate a messa [...]
+ "signingAlgorithm": { "index": 50, "kind": "parameter", "displayName":
"Signing Algorithm", "group": "security", "label": "security", "required":
false, "type": "enum", "javaType":
"org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [
"SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA",
"MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA",
"SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA",
"RIPEMD256 [...]
+ "signingCertificateChain": { "index": 51, "kind": "parameter",
"displayName": "Signing Certificate Chain", "group": "security", "label":
"security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The chain of certificates used to sign EDI
message." },
+ "signingPrivateKey": { "index": 52, "kind": "parameter", "displayName":
"Signing Private Key", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The key used to sign the EDI message." },
+ "sslContext": { "index": 53, "kind": "parameter", "displayName": "Ssl
Context", "group": "security", "label": "security", "required": false, "type":
"object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Set SSL context for connection to remote
server." },
+ "userName": { "index": 54, "kind": "parameter", "displayName": "User
Name", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The user-name that is used by the client for
basic authentication. If options for basic authen [...]
+ "validateSigningCertificateChain": { "index": 55, "kind": "parameter",
"displayName": "Validate Signing Certificate Chain", "group": "security",
"label": "security", "required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Certificates to validate the message's
signature ag [...]
},
"apis": {
"client": { "consumerOnly": false, "producerOnly": true, "description":
"Sends EDI Messages over HTTP", "methods": { "send": { "description": "Send
ediMessage to trading partner", "signatures": [
"org.apache.hc.core5.http.protocol.HttpCoreContext send(Object ediMessage,
String requestUri, String subject, String from, String as2From, String as2To,
org.apache.camel.component.as2.api.AS2MessageStructure as2MessageStructure,
String ediMessageContentType, String ediMessageCharset, String [...]
diff --git
a/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc
b/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc
index 8b3988308f01..b437954ec5e2 100644
--- a/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc
+++ b/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc
@@ -107,3 +107,39 @@ consumer applies its own keys and certificates and its own
asynchronous MDN sett
The settings of the shared server connection still come from the first
endpoint created on the port:
`sslContextParameters` and `sslContext` (used for the TLS listener and for the
https delivery of asynchronous MDNs),
`mdnMessageTemplate`, `server`, `serverFqdn` and `as2Version`. They are per
port, not per consumer.
+
+=== Sending through an HTTP proxy
+
+When outbound traffic must go through an HTTP forward proxy, set `proxyHost`
and `proxyPort` on the
+`as2://client/send` endpoint. The client connects to the proxy over plain http:
+
+* without an `sslContext`, the AS2 message is sent to the proxy with an
absolute request URI;
+* with an `sslContext`, the client asks the proxy to open a `CONNECT` tunnel
to `targetHostname` and
+`targetPortNumber`, and the TLS handshake and hostname verification are made
with the target through the tunnel.
+
+If the proxy requires authentication, set `proxyAuthUsername` and
`proxyAuthPassword`. They are sent to the
+proxy as a preemptive Basic `Proxy-Authorization` header, on the `CONNECT`
request when tunnelling, and are not
+sent to the AS2 partner. Setting only one of them is a configuration error:
the endpoint fails to start. If the
+proxy refuses the request, for example with `407 Proxy Authentication
Required`, the send fails with an exception,
+as the message has not reached the partner. The proxy options apply to the AS2
client only: asynchronous MDNs are
+delivered directly.
+
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:sendToPartner
+ steps:
+ - to:
+ uri: as2://client/send
+ parameters:
+ inBody: ediMessage
+ targetHostname: partner.example.com
+ targetPortNumber: 443
+ sslContext: "#partnerSslContext"
+ proxyHost: proxy.internal
+ proxyPort: 3128
+ proxyAuthUsername: "{{proxy.user}}"
+ proxyAuthPassword: "{{proxy.password}}"
+ # ... the AS2 message options
+----
diff --git
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
index 3bc6aec16704..fe023dd40c2b 100644
---
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
+++
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
@@ -135,6 +135,14 @@ public class AS2Configuration {
private String accessToken;
@UriParam(defaultValue = "false", label = "producer")
private boolean expectContinue;
+ @UriParam(label = "producer,proxy")
+ private String proxyHost;
+ @UriParam(label = "producer,proxy")
+ private Integer proxyPort;
+ @UriParam(label = "producer,proxy", security = "secret")
+ private String proxyAuthUsername;
+ @UriParam(label = "producer,proxy", security = "secret")
+ private String proxyAuthPassword;
@UriParam(label = "security", security = "secret")
private String mdnUserName;
@UriParam(label = "security", security = "secret")
@@ -655,6 +663,51 @@ public class AS2Configuration {
this.expectContinue = expectContinue;
}
+ public String getProxyHost() {
+ return proxyHost;
+ }
+
+ /**
+ * The host name of an HTTP proxy the client sends its AS2 messages
through. The connection to the proxy is plain
+ * http; when an SSL context is configured, the AS2 message goes through a
CONNECT tunnel to the target.
+ */
+ public void setProxyHost(String proxyHost) {
+ this.proxyHost = proxyHost;
+ }
+
+ public Integer getProxyPort() {
+ return proxyPort;
+ }
+
+ /**
+ * The port number of the HTTP proxy.
+ */
+ public void setProxyPort(Integer proxyPort) {
+ this.proxyPort = proxyPort;
+ }
+
+ public String getProxyAuthUsername() {
+ return proxyAuthUsername;
+ }
+
+ /**
+ * The user name for basic authentication with the HTTP proxy. The
credentials are sent to the proxy only.
+ */
+ public void setProxyAuthUsername(String proxyAuthUsername) {
+ this.proxyAuthUsername = proxyAuthUsername;
+ }
+
+ public String getProxyAuthPassword() {
+ return proxyAuthPassword;
+ }
+
+ /**
+ * The password for basic authentication with the HTTP proxy.
+ */
+ public void setProxyAuthPassword(String proxyAuthPassword) {
+ this.proxyAuthPassword = proxyAuthPassword;
+ }
+
public String getMdnUserName() {
return mdnUserName;
}
diff --git
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
index ceacff93c682..85b02e78d7a3 100644
---
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
+++
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
@@ -58,7 +58,8 @@ public final class AS2ConnectionHelper {
configuration.getTargetHostname(),
configuration.getTargetPortNumber(), configuration.getHttpSocketTimeout(),
configuration.getHttpConnectionTimeout(),
configuration.getHttpConnectionPoolSize(),
configuration.getHttpConnectionPoolTtl(),
configuration.getSslContext(),
- configuration.getHostnameVerifier(),
configuration.isExpectContinue());
+ configuration.getHostnameVerifier(),
configuration.isExpectContinue(), configuration.getProxyHost(),
+ configuration.getProxyPort(),
configuration.getProxyAuthUsername(), configuration.getProxyAuthPassword());
}
/**
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientProxyTest.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientProxyTest.java
new file mode 100644
index 000000000000..4efd60b8c0ad
--- /dev/null
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientProxyTest.java
@@ -0,0 +1,452 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.as2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ConnectException;
+import java.net.ServerSocket;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.as2.api.AS2ClientConnection;
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.camel.component.as2.api.AS2MessageStructure;
+import org.apache.camel.component.as2.api.AS2ServerConnection;
+import org.apache.camel.component.as2.api.AS2SignatureAlgorithm;
+import org.apache.camel.component.as2.api.entity.ApplicationEntity;
+import
org.apache.camel.component.as2.api.entity.DispositionNotificationMultipartReportEntity;
+import org.apache.camel.spi.Registry;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The AS2 client sends through an HTTP proxy: plain http in absolute form,
https through a CONNECT tunnel.
+ */
+public class AS2ClientProxyTest extends AbstractAS2ITSupport {
+
+ private static final String PROXY_USER = "proxyUser";
+ private static final String PROXY_PASSWORD = "proxyPassword";
+ private static final String PROXY_AUTHORIZATION = "Basic " +
Base64.getEncoder()
+ .encodeToString((PROXY_USER + ":" +
PROXY_PASSWORD).getBytes(StandardCharsets.UTF_8));
+ // a name the client cannot resolve (RFC 2606): only the proxy knows where
it is
+ private static final String PARTNER_BEHIND_PROXY = "as2-partner.invalid";
+ private static final String EDI_MESSAGE = """
+ UNB+UNOA:1+005435656:1+006415160:1+060515:1434+00000000000778'
+ UNH+00000000000117+INVOIC:D:97B:UN'
+ BGM+380+342459+9'
+ DTM+3:20060515:102'
+ UNT+23+00000000000117'
+ UNZ+1+00000000000778'
+ """;
+
+ private SSLContext sslContext;
+ private TestHttpProxy proxy;
+ private AS2ServerConnection httpServer;
+ private AS2ServerConnection httpsServer;
+ private CountingRequestHandler httpRequestHandler;
+ private CountingRequestHandler httpsRequestHandler;
+
+ @Override
+ public void setupResources() throws Exception {
+ sslContext = createSslContext();
+ proxy = new TestHttpProxy(PROXY_AUTHORIZATION);
+ proxy.addHostAlias(PARTNER_BEHIND_PROXY, "localhost");
+
+ httpServer = new AS2ServerConnection(
+ "1.1", "AS2ClientProxyTest Server", "server.example.com", 0,
AS2SignatureAlgorithm.SHA256WITHRSA,
+ null, null, null, "TBD", null, null, null, null, null);
+ httpRequestHandler = new CountingRequestHandler();
+ httpServer.listen("/", httpRequestHandler);
+
+ httpsServer = new AS2ServerConnection(
+ "1.1", "AS2ClientProxyTest Server", "server.example.com", 0,
AS2SignatureAlgorithm.SHA256WITHRSA,
+ null, null, null, "TBD", null, sslContext, null, null, null);
+ httpsRequestHandler = new CountingRequestHandler();
+ httpsServer.listen("/", httpsRequestHandler);
+ }
+
+ @Override
+ public void cleanupResources() {
+ if (httpServer != null) {
+ httpServer.close();
+ }
+ if (httpsServer != null) {
+ httpsServer.close();
+ }
+ if (proxy != null) {
+ proxy.close();
+ }
+ }
+
+ @Override
+ protected void bindToRegistry(Registry registry) {
+ registry.bind("tlsContext", sslContext);
+ }
+
+ @Test
+ public void sendsHttpThroughProxy() {
+ HttpEntity mdn = requestBodyAndHeaders("direct://HTTP", EDI_MESSAGE,
as2Headers());
+
+ assertInstanceOf(DispositionNotificationMultipartReportEntity.class,
mdn);
+ assertEquals(List.of("POST http://localhost:" +
httpServer.getLocalPort() + "/ HTTP/1.1"),
+ proxy.getRequestLines());
+ assertEquals(List.of(PROXY_AUTHORIZATION),
proxy.getProxyAuthorizations());
+ assertReceived(httpRequestHandler);
+ }
+
+ @Test
+ public void reachesPartnerOnlyResolvableByTheProxy() {
+ HttpEntity mdn =
requestBodyAndHeaders("direct://HTTP_PARTNER_BEHIND_PROXY", EDI_MESSAGE,
as2Headers());
+
+ assertInstanceOf(DispositionNotificationMultipartReportEntity.class,
mdn);
+ assertEquals(List.of("POST http://" + PARTNER_BEHIND_PROXY + ":" +
httpServer.getLocalPort() + "/ HTTP/1.1"),
+ proxy.getRequestLines());
+ assertReceived(httpRequestHandler);
+ }
+
+ @Test
+ public void refusedHttpRequestFailsTheSend() {
+ CamelExecutionException e = assertThrows(CamelExecutionException.class,
+ () -> requestBodyAndHeaders("direct://HTTP_WRONG_PASSWORD",
EDI_MESSAGE, as2Headers()));
+
+ assertTrue(rootMessage(e).contains("407"), rootMessage(e));
+ assertEquals(0, httpRequestHandler.getCount());
+ }
+
+ @Test
+ public void refusedHttpConnectionIsNotReused() {
+ assertEachSendIsRefused("direct://HTTP_WRONG_PASSWORD",
httpRequestLine());
+ assertEquals(0, httpRequestHandler.getCount());
+ }
+
+ @Test
+ public void refusedHttpConnectionIsNotReusedWhenTheProxyKeepsItOpen() {
+ // the 407 then has a body and the proxy waits for a retry on the same
connection: it must not be reused
+ proxy.setKeepAliveAfterRefusal(true);
+
+ assertEachSendIsRefused("direct://HTTP_WRONG_PASSWORD",
httpRequestLine());
+ assertEquals(0, httpRequestHandler.getCount());
+ }
+
+ @Test
+ public void sendsHttpsThroughProxyTunnel() {
+ HttpEntity mdn = requestBodyAndHeaders("direct://HTTPS", EDI_MESSAGE,
as2Headers());
+
+ assertInstanceOf(DispositionNotificationMultipartReportEntity.class,
mdn);
+ assertEquals(List.of(httpsConnectLine()), proxy.getRequestLines());
+ assertEquals(List.of(PROXY_AUTHORIZATION),
proxy.getProxyAuthorizations());
+ HttpRequest request = assertReceived(httpsRequestHandler);
+ // the proxy credentials are for the proxy only: they must not travel
through the tunnel to the partner
+ assertNull(request.getFirstHeader("Proxy-Authorization"));
+ }
+
+ @Test
+ public void sendsSeveralMessagesThroughTheTunnel() {
+ for (int i = 0; i < 3; i++) {
+ HttpEntity mdn = requestBodyAndHeaders("direct://HTTPS",
EDI_MESSAGE, as2Headers());
+
assertInstanceOf(DispositionNotificationMultipartReportEntity.class, mdn);
+ }
+
+ assertEquals(3, httpsRequestHandler.getCount());
+ // a connection kept alive by the pool is already tunnelled: it must
not be asked to CONNECT again
+
assertTrue(proxy.getRequestLines().stream().allMatch(httpsConnectLine()::equals),
proxy.getRequestLines()::toString);
+ assertTrue(proxy.getRequestLines().size() <= 3,
proxy.getRequestLines()::toString);
+ }
+
+ @Test
+ public void sendsConcurrentlyThroughTheTunnel() throws Exception {
+ int messages = 16;
+ ExecutorService executor = Executors.newFixedThreadPool(8);
+ try {
+ List<Future<Object>> mdns = new ArrayList<>();
+ for (int i = 0; i < messages; i++) {
+ mdns.add(executor.submit(() ->
requestBodyAndHeaders("direct://HTTPS", EDI_MESSAGE, as2Headers())));
+ }
+ for (Future<Object> mdn : mdns) {
+
assertInstanceOf(DispositionNotificationMultipartReportEntity.class,
mdn.get(30, TimeUnit.SECONDS));
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ assertEquals(messages, httpsRequestHandler.getCount());
+
assertTrue(proxy.getRequestLines().stream().allMatch(httpsConnectLine()::equals),
proxy.getRequestLines()::toString);
+ }
+
+ @Test
+ public void refusedTunnelFailsTheSend() {
+ CamelExecutionException e = assertThrows(CamelExecutionException.class,
+ () -> requestBodyAndHeaders("direct://HTTPS_WRONG_PASSWORD",
EDI_MESSAGE, as2Headers()));
+
+ assertTrue(rootMessage(e).contains("407"), rootMessage(e));
+ assertEquals(0, httpsRequestHandler.getCount());
+ }
+
+ @Test
+ public void refusedTunnelIsNotReused() {
+ // a connection without its tunnel must not go back to the pool, or
the next send skips CONNECT and TLS
+ assertEachSendIsRefused("direct://HTTPS_WRONG_PASSWORD",
httpsConnectLine());
+ assertEquals(0, httpsRequestHandler.getCount());
+ }
+
+ @Test
+ public void refusedTunnelIsNotReusedWhenTheProxyKeepsItOpen() {
+ proxy.setKeepAliveAfterRefusal(true);
+
+ assertEachSendIsRefused("direct://HTTPS_WRONG_PASSWORD",
httpsConnectLine());
+ assertEquals(0, httpsRequestHandler.getCount());
+ }
+
+ @Test
+ public void verifiesTheTargetHostNameThroughTheTunnel() {
+ // the certificate names localhost, which is also the proxy's name:
the handshake must check the target's
+ for (int i = 0; i < 2; i++) {
+ CamelExecutionException e =
assertThrows(CamelExecutionException.class,
+ () -> requestBodyAndHeaders("direct://HTTPS_IP_TARGET",
EDI_MESSAGE, as2Headers()));
+ assertTrue(hasCause(e, SSLException.class), rootMessage(e));
+ }
+
+ // a failed handshake leaves no connection in the pool: the second
send opens a new tunnel
+ String connectLine = "CONNECT 127.0.0.1:" + httpsServer.getLocalPort()
+ " HTTP/1.1";
+ assertEquals(List.of(connectLine, connectLine),
proxy.getRequestLines());
+ assertEquals(0, httpsRequestHandler.getCount());
+ }
+
+ @Test
+ public void unreachableProxyFailsAtStartup() throws Exception {
+ int closedPort;
+ try (ServerSocket socket = new ServerSocket(0)) {
+ closedPort = socket.getLocalPort();
+ }
+
+ assertThrows(ConnectException.class, () ->
createClientConnection("localhost", closedPort));
+ }
+
+ @Test
+ public void proxyPortIsRequired() {
+ IllegalArgumentException e =
assertThrows(IllegalArgumentException.class,
+ () -> createClientConnection("localhost", null));
+
+ assertTrue(e.getMessage().contains("proxyPort"), e.getMessage());
+ }
+
+ @Test
+ public void proxyAuthUsernameAloneIsRefused() {
+ IllegalArgumentException e =
assertThrows(IllegalArgumentException.class,
+ () -> createClientConnection("localhost", proxy.getPort(),
PROXY_USER, null));
+
+ assertEquals("proxyAuthUsername and proxyAuthPassword must be set
together", e.getMessage());
+ }
+
+ @Test
+ public void proxyAuthPasswordAloneIsRefused() {
+ IllegalArgumentException e =
assertThrows(IllegalArgumentException.class,
+ () -> createClientConnection("localhost", proxy.getPort(),
null, PROXY_PASSWORD));
+
+ assertEquals("proxyAuthUsername and proxyAuthPassword must be set
together", e.getMessage());
+ }
+
+ @Test
+ public void proxyAuthUsernameAloneFailsTheRouteStartup() {
+ // the endpoint creates its client connection when it is configured,
so the mistake is reported at startup
+ Exception e = assertThrows(Exception.class,
+ () ->
context.getEndpoint("as2://client/send?inBody=ediMessage&targetPortNumber=" +
httpServer.getLocalPort()
+ + "&proxyHost=localhost&proxyPort="
+ proxy.getPort() + "&proxyAuthUsername="
+ + PROXY_USER));
+
+ assertTrue(hasCause(e, IllegalArgumentException.class),
rootMessage(e));
+ assertEquals("proxyAuthUsername and proxyAuthPassword must be set
together", rootMessage(e));
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ public void configure() {
+ from("direct://HTTP")
+
.toF("as2://client/send?inBody=ediMessage&targetPortNumber=%d&proxyHost=localhost&proxyPort=%d"
+ + "&proxyAuthUsername=%s&proxyAuthPassword=%s",
+ httpServer.getLocalPort(), proxy.getPort(),
PROXY_USER, PROXY_PASSWORD);
+
+ from("direct://HTTP_PARTNER_BEHIND_PROXY")
+
.toF("as2://client/send?inBody=ediMessage&targetHostname=%s&targetPortNumber=%d"
+ +
"&proxyHost=localhost&proxyPort=%d&proxyAuthUsername=%s&proxyAuthPassword=%s",
+ PARTNER_BEHIND_PROXY,
httpServer.getLocalPort(), proxy.getPort(), PROXY_USER,
+ PROXY_PASSWORD);
+
+ from("direct://HTTP_WRONG_PASSWORD")
+
.toF("as2://client/send?inBody=ediMessage&targetPortNumber=%d&proxyHost=localhost&proxyPort=%d"
+ + "&proxyAuthUsername=%s&proxyAuthPassword=wrong",
+ httpServer.getLocalPort(), proxy.getPort(),
PROXY_USER);
+
+ from("direct://HTTPS")
+
.toF("as2://client/send?inBody=ediMessage&targetPortNumber=%d&sslContext=#tlsContext"
+ +
"&proxyHost=localhost&proxyPort=%d&proxyAuthUsername=%s&proxyAuthPassword=%s",
+ httpsServer.getLocalPort(), proxy.getPort(),
PROXY_USER, PROXY_PASSWORD);
+
+ from("direct://HTTPS_WRONG_PASSWORD")
+
.toF("as2://client/send?inBody=ediMessage&targetPortNumber=%d&sslContext=#tlsContext"
+ +
"&proxyHost=localhost&proxyPort=%d&proxyAuthUsername=%s&proxyAuthPassword=wrong",
+ httpsServer.getLocalPort(), proxy.getPort(),
PROXY_USER);
+
+ from("direct://HTTPS_IP_TARGET")
+
.toF("as2://client/send?inBody=ediMessage&targetHostname=127.0.0.1&targetPortNumber=%d"
+ +
"&sslContext=#tlsContext&proxyHost=localhost&proxyPort=%d"
+ + "&proxyAuthUsername=%s&proxyAuthPassword=%s",
+ httpsServer.getLocalPort(), proxy.getPort(),
PROXY_USER, PROXY_PASSWORD);
+ }
+ };
+ }
+
+ /**
+ * Sends twice through a route the proxy refuses: each send must get the
407 on a new connection to the proxy.
+ */
+ private void assertEachSendIsRefused(String route, String requestLine) {
+ for (int i = 0; i < 2; i++) {
+ CamelExecutionException e =
assertThrows(CamelExecutionException.class,
+ () -> requestBodyAndHeaders(route, EDI_MESSAGE,
as2Headers()));
+ assertTrue(rootMessage(e).contains("407"), rootMessage(e));
+ }
+
+ assertEquals(List.of(requestLine, requestLine),
proxy.getRequestLines());
+ assertEquals(2, proxy.getConnectionCount());
+ }
+
+ private String httpRequestLine() {
+ return "POST http://localhost:" + httpServer.getLocalPort() + "/
HTTP/1.1";
+ }
+
+ private String httpsConnectLine() {
+ return "CONNECT localhost:" + httpsServer.getLocalPort() + " HTTP/1.1";
+ }
+
+ private AS2ClientConnection createClientConnection(String proxyHost,
Integer proxyPort) throws IOException {
+ return createClientConnection(proxyHost, proxyPort, null, null);
+ }
+
+ private AS2ClientConnection createClientConnection(
+ String proxyHost, Integer proxyPort, String proxyAuthUsername,
String proxyAuthPassword)
+ throws IOException {
+ return new AS2ClientConnection(
+ "1.1", "Camel AS2 Endpoint", "example.org", "localhost",
httpServer.getLocalPort(), Duration.ofSeconds(5),
+ Duration.ofSeconds(5), 5, Duration.ofMinutes(15), null, null,
false, proxyHost, proxyPort,
+ proxyAuthUsername, proxyAuthPassword);
+ }
+
+ private static HttpRequest assertReceived(CountingRequestHandler
requestHandler) {
+ assertEquals(1, requestHandler.getCount());
+ HttpRequest request = requestHandler.getRequest();
+ assertNotNull(request);
+ HttpEntity entity = ((ClassicHttpRequest) request).getEntity();
+ assertInstanceOf(ApplicationEntity.class, entity);
+ assertEquals(EDI_MESSAGE, ((String) ((ApplicationEntity)
entity).getEdiMessage()).replaceAll("\r", ""));
+ return request;
+ }
+
+ private static boolean hasCause(Throwable e, Class<? extends Throwable>
type) {
+ for (Throwable cause = e; cause != null; cause = cause.getCause()) {
+ if (type.isInstance(cause)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String rootMessage(Throwable e) {
+ Throwable cause = e;
+ while (cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+ return String.valueOf(cause.getMessage());
+ }
+
+ private static Map<String, Object> as2Headers() {
+ Map<String, Object> headers = new HashMap<>();
+ headers.put("CamelAs2.requestUri", "/");
+ headers.put("CamelAs2.subject", "Test Case");
+ headers.put("CamelAs2.from", "[email protected]");
+ headers.put("CamelAs2.as2From", "878051556");
+ headers.put("CamelAs2.as2To", "878051556");
+ headers.put("CamelAs2.as2MessageStructure", AS2MessageStructure.PLAIN);
+ headers.put("CamelAs2.ediMessageContentType",
AS2MediaType.APPLICATION_EDIFACT);
+ headers.put("CamelAs2.ediMessageTransferEncoding", "7bit");
+ headers.put("CamelAs2.dispositionNotificationTo", "[email protected]");
+ headers.put("CamelAs2.attachedFileName", "");
+ return headers;
+ }
+
+ private static SSLContext createSslContext() throws Exception {
+ char[] password = "changeit".toCharArray();
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ try (InputStream is =
AS2ClientProxyTest.class.getResourceAsStream("/jsse/localhost.p12")) {
+ keyStore.load(is, password);
+ }
+ KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(keyStore, password);
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(keyStore);
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ return context;
+ }
+
+ private static final class CountingRequestHandler extends
AS2ClientManagerIT.RequestHandler {
+
+ private final AtomicInteger count = new AtomicInteger();
+
+ @Override
+ public void handle(ClassicHttpRequest request, ClassicHttpResponse
response, HttpContext context)
+ throws HttpException, IOException {
+ count.incrementAndGet();
+ super.handle(request, response, context);
+ }
+
+ int getCount() {
+ return count.get();
+ }
+ }
+}
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/TestHttpProxy.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/TestHttpProxy.java
new file mode 100644
index 000000000000..f93f79ad68b7
--- /dev/null
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/TestHttpProxy.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.as2;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.util.IOHelper;
+
+/**
+ * A minimal HTTP forward proxy for tests. It forwards plain http requests
sent in absolute form and opens CONNECT
+ * tunnels, and records the request line and the Proxy-Authorization header of
every request it receives.
+ * <p>
+ * It refuses a request without the required credentials with a 407. By
default it then closes the connection; it can
+ * also keep the connection open with a body in the 407, like a proxy waiting
for the client to retry with credentials.
+ */
+final class TestHttpProxy implements Closeable {
+
+ private final ServerSocket serverSocket;
+ private final String requiredAuthorization;
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+ private final List<Socket> sockets = new CopyOnWriteArrayList<>();
+ private final List<String> requestLines = new CopyOnWriteArrayList<>();
+ private final List<String> proxyAuthorizations = new
CopyOnWriteArrayList<>();
+ private final Map<String, String> hostAliases = new ConcurrentHashMap<>();
+ private final AtomicInteger connectionCount = new AtomicInteger();
+ private volatile boolean keepAliveAfterRefusal;
+
+ /**
+ * @param requiredAuthorization the Proxy-Authorization value the proxy
requires, or null to require none
+ */
+ TestHttpProxy(String requiredAuthorization) throws IOException {
+ this.requiredAuthorization = requiredAuthorization;
+ this.serverSocket = new ServerSocket(0, 50,
InetAddress.getLoopbackAddress());
+ executor.execute(this::acceptConnections);
+ }
+
+ int getPort() {
+ return serverSocket.getLocalPort();
+ }
+
+ List<String> getRequestLines() {
+ return requestLines;
+ }
+
+ List<String> getProxyAuthorizations() {
+ return proxyAuthorizations;
+ }
+
+ /**
+ * The number of connections on which the clients sent a request to the
proxy. A client's startup check opens a
+ * connection without sending anything, so it is not counted.
+ */
+ int getConnectionCount() {
+ return connectionCount.get();
+ }
+
+ /**
+ * Keeps the connection open after a 407, which carries a body, instead of
closing it.
+ */
+ void setKeepAliveAfterRefusal(boolean keepAliveAfterRefusal) {
+ this.keepAliveAfterRefusal = keepAliveAfterRefusal;
+ }
+
+ /**
+ * Makes the proxy resolve a host name the client itself cannot resolve,
like a partner only reachable through the
+ * proxy.
+ */
+ void addHostAlias(String host, String actualHost) {
+ hostAliases.put(host, actualHost);
+ }
+
+ @Override
+ public void close() {
+ IOHelper.close(serverSocket);
+ sockets.forEach(IOHelper::close);
+ executor.shutdownNow();
+ }
+
+ private void acceptConnections() {
+ while (!serverSocket.isClosed()) {
+ try {
+ Socket client = serverSocket.accept();
+ sockets.add(client);
+ executor.execute(() -> handle(client));
+ } catch (IOException e) {
+ // closed
+ }
+ }
+ }
+
+ private void handle(Socket client) {
+ try {
+ InputStream in = new BufferedInputStream(client.getInputStream());
+ OutputStream out = client.getOutputStream();
+ // a refused request is followed by the next one on the same
connection when it is kept alive
+ boolean firstRequest = true;
+ while (handleRequest(in, out, firstRequest)) {
+ firstRequest = false;
+ }
+ } catch (IOException e) {
+ // connection closed
+ } finally {
+ IOHelper.close(client);
+ }
+ }
+
+ /**
+ * @return true when the connection stays open for another request
+ */
+ private boolean handleRequest(InputStream in, OutputStream out, boolean
firstRequest) throws IOException {
+ String requestLine = readLine(in);
+ if (requestLine == null) {
+ return false;
+ }
+ if (firstRequest) {
+ connectionCount.incrementAndGet();
+ }
+ List<String> headers = new ArrayList<>();
+ String proxyAuthorization = null;
+ long contentLength = 0;
+ for (String header = readLine(in); header != null &&
!header.isEmpty(); header = readLine(in)) {
+ String name = header.substring(0, Math.max(header.indexOf(':'),
0)).trim().toLowerCase(Locale.ROOT);
+ String value = header.substring(header.indexOf(':') + 1).trim();
+ if ("proxy-authorization".equals(name)) {
+ proxyAuthorization = value;
+ } else {
+ if ("content-length".equals(name)) {
+ contentLength = Long.parseLong(value);
+ }
+ headers.add(header);
+ }
+ }
+ requestLines.add(requestLine);
+ proxyAuthorizations.add(String.valueOf(proxyAuthorization));
+
+ if (requiredAuthorization != null &&
!requiredAuthorization.equals(proxyAuthorization)) {
+ if (keepAliveAfterRefusal) {
+ // read the refused request's body, so the next request on
this connection can be read
+ in.skipNBytes(contentLength);
+ String body = "<html><body>Proxy authentication
required</body></html>";
+ write(out, "HTTP/1.1 407 Proxy Authentication
Required\r\nProxy-Authenticate: Basic realm=\"test\"\r\n"
+ + "Content-Type: text/html\r\nContent-Length: " +
body.length() + "\r\n\r\n" + body);
+ return true;
+ }
+ write(out, "HTTP/1.1 407 Proxy Authentication
Required\r\nProxy-Authenticate: Basic realm=\"test\"\r\n"
+ + "Content-Length: 0\r\nConnection: close\r\n\r\n");
+ return false;
+ }
+
+ String[] parts = requestLine.split(" ");
+ if ("CONNECT".equals(parts[0])) {
+ String[] hostPort = parts[1].split(":");
+ Socket target = connect(hostPort[0],
Integer.parseInt(hostPort[1]));
+ write(out, "HTTP/1.1 200 Connection established\r\n\r\n");
+ relay(in, out, target);
+ } else {
+ // forward in origin form, without the proxy's own credentials
+ URI uri = URI.create(parts[1]);
+ Socket target = connect(uri.getHost(), uri.getPort());
+ StringBuilder head = new StringBuilder(parts[0] + " " +
uri.getRawPath() + " " + parts[2] + "\r\n");
+ headers.forEach(h -> head.append(h).append("\r\n"));
+ write(target.getOutputStream(), head.append("\r\n").toString());
+ relay(in, out, target);
+ }
+ return false;
+ }
+
+ private Socket connect(String host, int port) throws IOException {
+ Socket target = new Socket(hostAliases.getOrDefault(host, host), port);
+ sockets.add(target);
+ return target;
+ }
+
+ private void relay(InputStream clientIn, OutputStream clientOut, Socket
target) throws IOException {
+ InputStream targetIn = target.getInputStream();
+ executor.execute(() -> {
+ try {
+ targetIn.transferTo(clientOut);
+ } catch (IOException e) {
+ // connection closed
+ }
+ });
+ try {
+ clientIn.transferTo(target.getOutputStream());
+ } finally {
+ IOHelper.close(target);
+ }
+ }
+
+ private static void write(OutputStream out, String s) throws IOException {
+ out.write(s.getBytes(StandardCharsets.ISO_8859_1));
+ out.flush();
+ }
+
+ private static String readLine(InputStream in) throws IOException {
+ ByteArrayOutputStream line = new ByteArrayOutputStream();
+ int b;
+ while ((b = in.read()) != -1 && b != '\n') {
+ if (b != '\r') {
+ line.write(b);
+ }
+ }
+ return b == -1 && line.size() == 0 ? null :
line.toString(StandardCharsets.ISO_8859_1);
+ }
+}
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java
index 97e5a5dc260e..f5e5086106c8 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java
@@ -1749,6 +1749,81 @@ public interface AS2EndpointBuilderFactory {
doSetProperty("expectContinue", expectContinue);
return this;
}
+ /**
+ * The password for basic authentication with the HTTP proxy.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: proxy
+ *
+ * @param proxyAuthPassword the value to set
+ * @return the dsl builder
+ */
+ default AS2EndpointProducerBuilder proxyAuthPassword(String
proxyAuthPassword) {
+ doSetProperty("proxyAuthPassword", proxyAuthPassword);
+ return this;
+ }
+ /**
+ * The user name for basic authentication with the HTTP proxy. The
+ * credentials are sent to the proxy only.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: proxy
+ *
+ * @param proxyAuthUsername the value to set
+ * @return the dsl builder
+ */
+ default AS2EndpointProducerBuilder proxyAuthUsername(String
proxyAuthUsername) {
+ doSetProperty("proxyAuthUsername", proxyAuthUsername);
+ return this;
+ }
+ /**
+ * The host name of an HTTP proxy the client sends its AS2 messages
+ * through. The connection to the proxy is plain http; when an SSL
+ * context is configured, the AS2 message goes through a CONNECT tunnel
+ * to the target.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: proxy
+ *
+ * @param proxyHost the value to set
+ * @return the dsl builder
+ */
+ default AS2EndpointProducerBuilder proxyHost(String proxyHost) {
+ doSetProperty("proxyHost", proxyHost);
+ return this;
+ }
+ /**
+ * The port number of the HTTP proxy.
+ *
+ * The option is a: <code>java.lang.Integer</code> type.
+ *
+ * Group: proxy
+ *
+ * @param proxyPort the value to set
+ * @return the dsl builder
+ */
+ default AS2EndpointProducerBuilder proxyPort(Integer proxyPort) {
+ doSetProperty("proxyPort", proxyPort);
+ return this;
+ }
+ /**
+ * The port number of the HTTP proxy.
+ *
+ * The option will be converted to a <code>java.lang.Integer</code>
+ * type.
+ *
+ * Group: proxy
+ *
+ * @param proxyPort the value to set
+ * @return the dsl builder
+ */
+ default AS2EndpointProducerBuilder proxyPort(String proxyPort) {
+ doSetProperty("proxyPort", proxyPort);
+ return this;
+ }
/**
* The access token that is used by the client for bearer
* authentication.