Mule 4.1.4 通过 HTTP POST 请求上传压缩 xml 文件内容失败

Mule 4.1.4 Failing upload compressed xml file content through HTTP POST request

我正在将 Mule 3.9 文件上传逻辑迁移到 Mule 4.1.4 版本。为简单起见,在 Mule 4.1.4 中,我尝试使用 http 连接器的基本逻辑将压缩的 xml 文件内容上传到 post 到 HTTP POST 请求,它一直失败 BAD_REQUEST, 没有得到我的输入有什么问题。 请建议我在其中缺少什么???

Mule 3.9 现有工作代码:

<flow name="Post_XML_To_ExtSystem" processingStrategy="synchronous">
        <timer-interceptor/>
        <object-to-byte-array-transformer doc:name="Object to Byte Array"/>
        <gzip-compress-transformer doc:name="Gzip Compress"/>
        <logger message="gZip compression completed for Part: #[flowVars.partId]" level="INFO" doc:name="gZip completed"/>
        <flow-ref name="WriteToFile_Flow" doc:name="Write to File Optionally"/>
        <set-variable variableName="fileContentgzip" value="#[payload]" doc:name="fileContentgzip"/>
        <flow-ref name="SetAttachments_PostPayload_Flow" doc:name="SetAttachments_PostPayload_Flow - FlowRef"/>
        <exception-strategy ref="Global_Errorflow_Choice_Exception_Strategy" doc:name="Reference Exception Strategy"/>
</flow>

<sub-flow name="SetAttachments_PostPayload_Flow">
        <logger message="Post Payload Flow with vars: #[flowVars]" level="DEBUG" doc:name="Logger"/>
        <set-attachment attachmentName="TenantID" value="#['${http.ext.system.tenant}']" contentType="text/plain" doc:name="Tenant ID"/>
        <set-attachment attachmentName="Category" value="#[flowVars.Category]" contentType="text/plain" doc:name="Category"/>
        <set-attachment attachmentName="Data" value="#[flowVars.fileContentgzip]" contentType="application/xml" doc:name="Data"/>
        <scripting:component doc:name="filename attachment">
            <scripting:script engine="Groovy"><![CDATA[import org.mule.message.ds.ByteArrayDataSource;
import javax.activation.DataHandler;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

String category = message.getInvocationProperty("Category")
String fileName=category + '.xml'
String attachmentName='Data'

byte[] compressed = flowVars.fileContentgzip
ByteArrayDataSource attachment = new ByteArrayDataSource(compressed, "application/xml",fileName);
message.addOutboundAttachment(attachmentName, new DataHandler(attachment))

return payload;]]></scripting:script>
        </scripting:component>
        <copy-attachments attachmentName="*" doc:name="All attachments together"/>
        <set-payload value="#[null]" doc:name="Nullify Payload"/>
        <logger message="before ingestion call: ${http.by.ingestion.basepath}, ${http.by.ingestion.host}, ${http.by.ingestion.port}" level="DEBUG" doc:name="Log Ingestion basepath, host, port"/>
        <logger message="Begin Posting #[flowVars.Category] for Part: #[flowVars.partId]" level="INFO" doc:name="Begin Posting data"/>
        <flow-ref name="Ingestion_with_retries_Flow" doc:name="Flow Ref Ingestion_with_retries" doc:description="retry injestion api call"/>
</sub-flow>

<flow name="Ingestion_with_retries_Flow" >
        <until-successful objectStore-ref="objectStore" maxRetries="${max.retries}" deadLetterQueue-ref="Failed_Payload_To_ErrorDir_And_Notify"
                          failureExpression="#[(exception != null) and (exception.causedBy(java.net.ConnectException) || exception.causedBy(java.net.SocketTimeoutException) || exception.causedBy(java.net.SocketException) || exception.causedBy(java.io.IOException))]"
                          doc:name="Until Successful" millisBetweenRetries="${millis.between.retries}">
            <processor-chain doc:name="Processor Chain">
                <logger message="Posting data to Server" level="INFO" doc:name="Logger"/>
                <http:request config-ref="HTTPS_Ingestion_Service_ExtSystem" path="/delivery" method="POST" doc:name="ExtSystem Data Delivery Post">
                    <http:request-builder>
                        <http:header headerName="Accept" value="${http.by.interface.version}"/>
                        <http:header headerName="Content-Encoding" value="gzip"/>
                    </http:request-builder>
                    <http:success-status-code-validator values="200"/>
                </http:request>

                <json:xml-to-json-transformer doc:name="XML to JSON"/>
                <flow-ref name="Subflow_Extract_Ingestion_Response" doc:name="Extract Ingestion Response"/>
            </processor-chain>
        </until-successful>
</flow>

<sub-flow name="Subflow_Extract_Ingestion_Response">
        <object-to-string-transformer returnClass="java.lang.String" mimeType="application/json" doc:name="Response_to_String"/>

        <dw:transform-message doc:name="Extract DeliveryId">
            <dw:set-payload resource="classpath:ingestion\ingestion-delivery.dwl"/>
        </dw:transform-message>
        <json:json-to-object-transformer returnClass="java.lang.Object" doc:name="JSON to Object"/>
        <set-variable variableName="ExtSystemDeliveryID" value="#[payload.DeliveryID]" doc:name="ExtSystemDeliveryID"/>
        <logger message="Delivery ID: #[payload.DeliveryID]" level="INFO" doc:name="Log Delivery Id"/>
        <set-variable variableName="ExtSystemStatus" value="#[payload.Status]" doc:name="ExtSystemStatusStatus"/>
        <flow-ref name="Update_DeliveryID_Category_in_Part_Flow" doc:name="Update Part with DeliveryID and Category"/>
        <set-payload value="#[payload + '\n']" doc:name="Set Payload"/>
        <file:outbound-endpoint path="${write.folderpath}#[flowVars.correlationId]" outputPattern="HttpResponse_IngestionIDs.txt" connector-ref="File" responseTimeout="10000" doc:name="Write Ingestion Response"/>
        <logger message="Ingestion response stored at ${write.folderpath}#[flowVars.batchJobInstanceId]/#[flowVars.Category]_#[flowVars.partId].gz" level="INFO" doc:name="Log response path"/>
 </sub-flow>

Mule 4.1.4 XML 压缩文件上传逻辑

<flow name="storeStocksFlow" doc:id="2d611c4c-edec-4b75-aa94-25474d145040" >
        <http:listener doc:name="POST/payloadtest" doc:id="a5ed0fce-aa12-4e00-a68c-fe99008f1559" allowedMethods="POST" config-ref="HTTP_Listener_config" path="/payloadtest" outputMimeType="application/json">
        </http:listener>
        <logger level="INFO" doc:name="Logger" doc:id="61545cb4-8d94-4af8-a08e-9bfd0667b77f" message="Input json request: #[payload]"/>
        <set-variable value="#[payload]" doc:name="Set Variable" doc:id="3923534b-7482-4a8c-ad46-948fda597550" variableName="origJsonInPayload"/>       
        <set-variable value="#[uuid()]" doc:name="Set Variable correlationId" doc:id="49798fd3-3175-44f8-9443-368b9a018207" variableName="correlationId"/>
        <logger level="INFO" doc:name="Logger before transformation" doc:id="c72a768f-58c4-4947-b29c-93aa955b18a5" message="Before transformation: #[payload]"/>
        <logger level="INFO" doc:name="Logger after transformation" message="Logger after transformation: #[payload]" doc:id="287ee190-47f6-4af6-982e-6f93a66cc052"/>
        <!-- Tried both compressed and plain xml format both giving BAD_REQUEST error
        <compression:compress doc:name="Gzip Compress" doc:id="bf8e4d8e-dbce-43f8-982a-ff68b87839c0" >
            <compression:compressor >
                <compression:gzip-compressor />
            </compression:compressor>
        </compression:compress> -->
        <logger message="gzip compression completed - payload:#[payload]" level="INFO" doc:name="gZip completed" />
        <set-variable variableName="fileContentgzip" value="#[payload]" doc:name="fileContentgzip" />
        <set-variable variableName="TenantID" value="#['${http.ext.system.tenant}']" mimeType="application/json" doc:name="Tenant ID"/>
        <set-variable variableName="Category" value="Stocks" mimeType="application/json" doc:name="Category"/>
        <set-variable variableName="Data" value="#[vars.fileContentgzip]" mimeType="application/xml" doc:name="Data"/>
        <logger level="INFO" doc:name="Logger" doc:id="29a22776-354e-42b0-b486-36bedcf8d6f0" message="JOB entry created in JOB table."/>
        <flow-ref name="BY_API_Call_SubFlow1" doc:name="ExtSystem Ingestion API Test"/>
 </flow>

 <sub-flow name="API_Call_SubFlow1">
        <logger message="Posting data to Server" level="INFO" doc:name="Logger" />
        <http:request config-ref="HTTPS_Ingestion_Service_ExtSystem" path="/delivery" method="POST" doc:name="Ext System Data Delivery Post" outputMimeType="application/xml">
            <http:body><![CDATA[#[%dw 2.0
output application/xml
input payload multipart/form-data
---
{
  parts : {
    Data : {
      headers : {
        "Content-Disposition" : {
          "name" : "Data",
          "filename": "Stocks.xml"
        },
        "Accept" : 'application/xml',
        "Content-Encoding": 'gzip',
        "TenantID": "xxxx-yyyy-aaaa-bbbb-ccccccc",
        "Category": "Stocks"
      },
      content : payload
    }
  }
}]]]></http:body>
            <http:headers ><![CDATA[#[output application/java
---
{
    "Content-Type" : "application/com.ext-system.xxx_and_yyy-v1.14.17+xml"
}]]]></http:headers>
            <http:response-validator >
                <http:success-status-code-validator values="200" />
            </http:response-validator>
        </http:request>
        <logger level="INFO" doc:name="Ingestion API Response" doc:id="a9ece74e-4b86-486f-9f3c-16272d1d00d1" message="Ingestion API Response: #[payload]"/>
</sub-flow>

错误日志:

0-6ffbd441-5963-11e9-8d2b-0a0027000005] org.mule.runtime.core.internal.processor.LoggerMessageProcessor: Posting data to Server
ERROR 2019-04-08 00:02:04,461 [[MuleRuntime].cpuLight.16: [adapter].storePersonFlow.CPU_LITE @427b75e6] [event: ] org.mule.runtime.core.internal.exception.OnErrorContinueHandler: 
********************************************************************************
Message               : HTTP POST on resource 'https://api.ext-system.com:443/xxxx/delivery' failed: bad request (400).
Error type            : HTTP:BAD_REQUEST
Element               : API_Call_SubFlow1/processors/1 @ adapter:exposing-a-restful-resource-using-the-http-connector.xml:142 (Ext System Data Delivery Post)
Element XML           : <http:request config-ref="HTTPS_Ingestion_Service_ExtSystem" path="/delivery" method="POST" doc:name="Ext System Data Delivery Post" outputMimeType="application/xml">
<http:body>#[%dw 2.0
output application/xml
input payload multipart/form-data
---
{
  parts : {
    Data : {
      headers : {
        "Content-Disposition" : {
          "name" : "Data",
          "filename": "Stocks.xml"
        },
        "Accept" : 'application/com.ext-system.xxx_and_yyy-v1.14.17+xml',
        "Content-Encoding": 'gzip',
        "TenantID": "xxxx-yyyy-aaaa-bbbb-ccccccc",
        "Category": "Stocks"
      },
      content : payload
    }
  }
}]</http:body>
<http:headers>#[output application/xml
---
{
    "Content-Type" : "application/com.ext-system.xxx_and_yyy-v1.14.17+xml"
}]</http:headers>
<http:response-validator>
<http:success-status-code-validator values="200"></http:success-status-code-validator>
</http:response-validator>
</http:request>

  (set debug level logging or '-Dmule.verbose.exceptions=true' for everything)
********************************************************************************

INFO  2019-04-08 00:02:04,466 [[MuleRuntime].cpuLight.16: [adapter].storePersonFlow.CPU_LITE @427b75e6] [event: 0-6ffbd441-5963-11e9-8d2b-0a0027000005] org.mule.runtime.core.internal.processor.LoggerMessageProcessor: In HTTP:BAD_REQUEST

主机返回错误,因为它不喜欢请求的某些内容。仅通过查看代码片段很难理解可能出现的问题。一张完整的图片重新查询可以知道服务器验证失败的数据和详情。

如果您有工作案例(3.9 版本),解决此问题的方法是在两个版本中启用 HTTP 有线日志记录 (https://support.mulesoft.com/s/article/How-to-Enable-HTTP-Wire-Logging),执行它们并比较两个 HTTP 请求。然后你可以看到它们之间有什么不同,并调整 Mule 4 版本以匹配其他请求。

我找到了根本原因,请求中缺少 headers,按照 alejandro-dobniewski 的建议,在 log4j2.xml 中启用 DEBUG 后我可以追踪到这个问题。在我的用例中,TenantID、类别(这两个键的值都是字符串)和数据是属于 multipart/form-data 的键。数据值将是 gzip 文件内容。下面 json 是不言自明的。

解法:

HTTP 请求的 headers 和 body(重要的是部分有效负载 json)的正确格式是:

<http:body ><![CDATA[#[%dw 2.0
output multipart/form-data
---
{
  parts: {
    TenantID : {
      headers : {
        "Content-Type": "text/plain"
      },
      content : "xxxxxxxxx"
    },
    Category : {
      headers : {
        "Content-Type": "text/plain"
      },
      content : "MyCategory"
    },
    Data: {
      headers: {
        "Content-Disposition": {
          "name": "Data",
          "filename": "MyCategory_gzip"
        },
        "Content-Type": payload.^mimeType,
      },
      content: payload
    }
  }
}]]]></http:body>
            <http:headers ><![CDATA[#[output application/java
---
{
    "Accept" : "application/com.xxxxx-v1.1+xml",
    "Content-Encoding" : "gzip"
}]]]></http:headers>