如何通过GAS中的SOAP API发送带附件的DIME消息?

时间:2016-01-08 15:17:40

标签: web-services soap google-apps-script soap-client tridion-worldserver

我有一个SDL的WorldServer实例,我试图通过SOAP访问以创建新项目。

我正在考虑通过Google Apps脚本创建一个网络应用,用户可以将文件上传到我可以用来发送和制作项目/报价的blob中。我能够让SOAP在任何不需要附件的命令中正常工作。但添加附件很复杂。

我通过soapui成功完成了这个(但是,soapui为我处理DIME格式,所以我假设我需要使用DIME?),但是当我尝试通过手动生成相同的SOAP消息时消息我自己,我得到一个SOAP错误说明:

<faultcode>soapenv:Server.userException</faultcode>
<faultstring>java.io.IOException: Stream closed.</faultstring>
<detail><ns1:hostname xmlns:ns1="http://xml.apache.org/axis/">SNJWS112</ns1:hostname></detail>

以下是我使用的示例代码:

function makeDimeHeader (position, isChunk, id, contentType, data) {
  // ref: http://xml.coverpages.org/draft-nielsen-dime-02.txt
  var bin = ""

  // version 1
  bin += "00001"

  if (position == "start") {
    bin += "10"
  } else if (position == "finish") {
    bin += "01"
  } else if (position == "middle") {
    bin += "00"
  } else if (position == "only") {
    bin += "11"
  } else {
    throw "Error: position must be 'start', 'only', 'middle', or 'finish'"
  }

  if (isChunk) {
    bin += "1"
  } else {
    bin += "0"
  }

  if (id.constructor !== Array) throw "Error: id must be a byte array"
  if (contentType.constructor !== Array) throw "Error: contentType must be a byte array"
  if (data.constructor !== Array) throw "Error: data must be a byte array"

  var textType = Utilities.newBlob(contentType).getDataAsString()

  if (textType.slice(0,4) == "http") {
    bin += "0010"
  } else if (contentType.length == 0) {
    bin += "0000"
  } else {
    bin += "0001"
  }

  // RESERVED
  bin += "0000"

  // OPTIONS LENGTH
  bin += "0000000000000000"

  bin += ("0000000000000000" + id.length.toString(2)).slice(-16)

  bin += ("0000000000000000" + contentType.length.toString(2)).slice(-16)

  bin += ("00000000000000000000000000000000" + data.length.toString(2)).slice(-32)

  var bytes = []
  for (var i = 0; i < bin.length/8; i++) {
    num = parseInt(bin.slice(i*8,(i*8)+8),2);
    // Byte[] is an Int8Array, not Uint8Array
    if (num > 127) num = num - 256
    bytes.push(num)
  }

  // PADDING TO CLOSEST 4 BYTES
  if (id.length % 4 != 0) bytes = bytes.concat([0, 0, 0].slice(0, 4 - (id.length % 4)))
  bytes = bytes.concat(id)

  // PADDING TO CLOSEST 4 BYTES
  if (contentType.length % 4 != 0) bytes = bytes.concat([0, 0, 0].slice(0, 4 - (contentType.length % 4)))
  bytes = bytes.concat(contentType)

  // PADDING TO CLOSEST 4 BYTES
  if (data.length % 4 != 0) bytes = bytes.concat([0, 0, 0].slice(0, 4 - (data.length % 4)))
  bytes = bytes.concat(data)

  return bytes

}

function testPost() {
  var file = UrlFetchApp.fetch("https://.../somedoc.xlsx");
  var fileBlob = file.getBlob();
  var fileBytes = fileBlob.getBytes();

  var contentType = 'application/dime; charset=Utf-8';

  var data = Utilities.newBlob(
    "<?xml version=\"1.0\"?>\r\n" +
    "<soapenv:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:com=\"http://www.idiominc.org/com.idiominc.webservices.WorkflowWSWorkflowManager\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
    "<soapenv:Header/>" +
    "<soapenv:Body>" +
    "<com:createProjectGroup7_ soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
    "<token xsi:type=\"xsd:string\">0000000000</token>" +
    "<name xsi:type=\"xsd:string\">TEST NEW PROJECT</name>" +
    "<locales xsi:type=\"data:stringArray\" soapenc:arrayType=\"xsd:string[]\" xmlns:data=\"http://webservices.idiominc.com/data\">" +
    "<item type=\"xsd:string\">English (United States)</item>" +
    "<item type=\"xsd:string\">Korean</item>" +
    "</locales>" +
    "<attachedFile xsi:type=\"data:stringArray\" soapenc:arrayType=\"xsd:string[]\" xmlns:data=\"http://webservices.idiominc.com/data\">" +
    "<file type=\"xsd:string\">test.xlsx</file>" +
    "</attachedFile>" +
    "<client xsi:type=\"xsd:string\">TEST CLIENT</client>" +
    "<projectType xsi:type=\"xsd:string\">TEST PROJECT TYPE</projectType>" +
    "<customAisProperties xsi:type=\"data:stringArray\" soapenc:arrayType=\"xsd:string[]\" xmlns:data=\"http://webservices.idiominc.com/data\"/>" +
    "</com:createProjectGroup7_>" +
    "</soapenv:Body>" +
    "</soapenv:Envelope>").getBytes()

  var soapcontent = Utilities.newBlob(
    "http://schemas.xmlsoap.org/soap/envelope/"
    ).getBytes()

  var firstSoapDime = makeDimeHeader("start", false, [], soapcontent, data)

  var filecontent = Utilities.newBlob(
    "application/octet-stream"
    ).getBytes()

  var lastFileData = makeDimeHeader("finish", false, Utilities.newBlob("test.xlsx").getBytes(), filecontent, fileBytes)

  var payloadbytes = firstSoapDime.concat(lastFileData)

  var headers =
      {
        "SOAPAction" : ""
      };

  var payload = Utilities.newBlob(payloadbytes).getDataAsString();

  var options =
      {
        "method" : "post",
        "headers" : headers,
        "contentType": contentType,
        "contentLength": payloadbytes.length,
        "payload" : payload,
        "muteHttpExceptions" : true
      };

  var response = UrlFetchApp.fetch("https://<ourdomain>.sdlproducts.com/ws/services/WorkflowWSWorkflowManager", options);
  Logger.log(payload)
  Logger.log(response)
};

任何人都可以查看我的代码并查看任何问题,或建议其他任何替代方案吗?

1 个答案:

答案 0 :(得分:0)

我在原始代码中发现了问题。

  1. DIME消息中的0x00填充在数据之后,而不是之前。
  2. 我需要将有效负载作为Byte []数组附加而不是字符串。但是,这不能是我连接2个或更多Byte []数组的地方,因为我认为它们被视为普通数组。所以我通过Utilities.newBlob()。getBytes()运行它来制作一个新的Byte []数组以防万一。这是让它通过的最后一块。
  3. 正确的代码如下。

    function makeDimeHeader (position, isChunk, id, contentType, data) {
      // ref: http://xml.coverpages.org/draft-nielsen-dime-02.txt
      var bin = ""
    
      // version 1
      bin += "00001"
    
      if (position == "start") {
        bin += "10"
      } else if (position == "finish") {
        bin += "01"
      } else if (position == "middle") {
        bin += "00"
      } else if (position == "only") {
        bin += "11"
      } else {
        throw "Error: position must be 'start', 'only', 'middle', or 'finish'"
      }
    
      if (isChunk) {
        bin += "1"
      } else {
        bin += "0"
      }
    
      if (id.constructor !== Array) throw "Error: id must be a byte array"
      if (contentType.constructor !== Array) throw "Error: contentType must be a byte array"
      if (data.constructor !== Array) throw "Error: data must be a byte array"
    
      var textType = Utilities.newBlob(contentType).getDataAsString()
    
      if (textType.slice(0,4) == "http") {
        bin += "0010"
      } else if (contentType.length == 0) {
        bin += "0000"
      } else {
        bin += "0001"
      }
    
      // RESERVED
      bin += "0000"
    
      // OPTIONS LENGTH
      bin += "0000000000000000"
    
      bin += ("0000000000000000" + id.length.toString(2)).slice(-16)
    
      bin += ("0000000000000000" + contentType.length.toString(2)).slice(-16)
    
      bin += ("00000000000000000000000000000000" + data.length.toString(2)).slice(-32)
    
      var bytes = []
      for (var i = 0; i < bin.length/8; i++) {
        num = parseInt(bin.slice(i*8,(i*8)+8),2);
        // Byte[] is an Int8Array, not Uint8Array
        if (num > 127) num = num - 256
        bytes.push(num)
      }
    
      bytes = bytes.concat(id)
      // PADDING TO CLOSEST 4 BYTES
      if (id.length % 4 != 0) bytes = bytes.concat([0, 0, 0].slice(0, 4 - (id.length % 4)))
    
      bytes = bytes.concat(contentType)
      // PADDING TO CLOSEST 4 BYTES
      if (contentType.length % 4 != 0) bytes = bytes.concat([0, 0, 0].slice(0, 4 - (contentType.length % 4)))
    
      bytes = bytes.concat(data)
      // PADDING TO CLOSEST 4 BYTES
      if (data.length % 4 != 0) bytes = bytes.concat([0, 0, 0].slice(0, 4 - (data.length % 4)))
    
      return bytes
    
    }
    
    function testPost() {
      var file = UrlFetchApp.fetch("https://.../somedoc.xlsx");
      var fileBlob = file.getBlob();
      var fileBytes = fileBlob.getBytes();
    
      var contentType = 'application/dime';
    
      var data = Utilities.newBlob(
        "<?xml version=\"1.0\"?>\r\n" +
        "<soapenv:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:com=\"http://www.idiominc.org/com.idiominc.webservices.WorkflowWSWorkflowManager\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
        "<soapenv:Header/>" +
        "<soapenv:Body>" +
        "<com:createProjectGroup7_ soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
        "<token xsi:type=\"xsd:string\">0000000000</token>" +
        "<name xsi:type=\"xsd:string\">TEST NEW PROJECT</name>" +
        "<locales xsi:type=\"data:stringArray\" soapenc:arrayType=\"xsd:string[]\" xmlns:data=\"http://webservices.idiominc.com/data\">" +
        "<item type=\"xsd:string\">English (United States)</item>" +
        "<item type=\"xsd:string\">Korean</item>" +
        "</locales>" +
        "<attachedFile xsi:type=\"data:stringArray\" soapenc:arrayType=\"xsd:string[]\" xmlns:data=\"http://webservices.idiominc.com/data\">" +
        "<file type=\"xsd:string\">test.xlsx</file>" +
        "</attachedFile>" +
        "<client xsi:type=\"xsd:string\">TEST CLIENT</client>" +
        "<projectType xsi:type=\"xsd:string\">TEST PROJECT TYPE</projectType>" +
        "<customAisProperties xsi:type=\"data:stringArray\" soapenc:arrayType=\"xsd:string[]\" xmlns:data=\"http://webservices.idiominc.com/data\"/>" +
        "</com:createProjectGroup7_>" +
        "</soapenv:Body>" +
        "</soapenv:Envelope>").getBytes()
    
      var soapcontent = Utilities.newBlob(
        "http://schemas.xmlsoap.org/soap/envelope/"
        ).getBytes()
    
      var firstSoapDime = makeDimeHeader("start", false, [], soapcontent, data)
    
      var filecontent = Utilities.newBlob(
        "application/octet-stream"
        ).getBytes()
    
      var lastFileData = makeDimeHeader("finish", false, Utilities.newBlob("test.xlsx").getBytes(), filecontent, fileBytes)
    
      var payloadbytes = firstSoapDime.concat(lastFileData)
    
      var headers =
          {
            "SOAPAction" : ""
          };
    
      var payload = Utilities.newBlob(payloadbytes).getBytes();
    
      var options =
          {
            "method" : "post",
            "headers" : headers,
            "contentType": contentType,
            "contentLength": payloadbytes.length,
            "payload" : payload,
            "muteHttpExceptions" : true
          };
    
      var response = UrlFetchApp.fetch("https://<ourdomain>.sdlproducts.com/ws/services/WorkflowWSWorkflowManager", options);
      Logger.log(payload)
      Logger.log(response)
    };