Friday, November 29, 2019

Google Cloud has the fastest build now - a.k.a. හූ හූ, AWS!

SLAppForge Sigma cloud IDE - for an ultra-fast serverless ride! (courtesy of Pexels)

Building a deployable serverless code artifact is a key functionality of our SLAppForge Sigma cloud IDE - the first-ever, completely browser-based solution for serverless development. The faster the serverless build, the sooner you can get along with the deployment - and the sooner you get to see your serverless application up and running.

AWS was "slow" too - but then came Quick Build.

During early days, back when we supported only AWS, our mainstream build was driven by CodeBuild. This had several drawbacks; it usually took 10-20 seconds for the build to complete, and it was rather repetitive - cloning the repo and downloading dependencies each time. Plus, you only get 100 free build minutes per month, so it adds a bit of a cost - despite small - to ourselves, as well as to our users.

Then we noticed that we only need to modify the previous build artifact in order to get the new code rolling. So I wrote a "quick build"; basically a Lambda that downloads the last build artifact, updates the zipfile with the changed code files, and re-uploads it as the current artifact. This was accompanied by a "quick deploy" that directly updates the code of affected functions, thereby avoiding the overhead of a complete CloudFormation deployment.

Then our ex-Adroit wizard Chathura built a test environment, and things changed drastically. The test environment (basically a warm Lambda, replicating the content of the user's project) already had everything; all code files and dependencies, pre-loaded. Now "quick build" was just a matter of zipping everything up from within the test environment itself, and uploading it to S3; just one network call instead of two.

GCP build - still in stone age?

When we introduced GCP support, the build was again based on their Cloud Build, a.k.a. Container Builder service. Although GCP did offer 3600(!) free build minutes per month (120 each day; see what I'm talking, AWS?), theirs was generally slower than CodeBuild. So, for several months, Sigma's GCP support had the bad reputation of having the slowest build-deployment cycle.

But now, it is no longer the case.

Wait, what? It only needs code - no dependencies?

There's a very interesting characteristic of Cloud Functions:

When you deploy your function, Cloud Functions installs dependencies declared in the package.json file using the npm install command.

-Google Cloud Functions: Specifying dependencies in Node.js

This means, for deploying, you just have to upload a zipfile containing the sources and a dependencies file (package.json, requirements.txt and the like). No more npm install, or megabyte-sized bundle uploads.

But, the coolest part is...

... you can do it completely within the browser!

jszip FTW!

That awesome jszip package does it all for us, in just a couple lines:

let zip = new JSZip();

files.forEach(file => zip.file(file.name, file.code));

/*
a bit more complex, actually - e.g. for a nested file 'folder/file'
zip.folder(folder.name).file(file.name, file.code)
*/

let data = await zip.generateAsync({
 type: "string",
 streamFiles: true,
 compression: "DEFLATE"
});

We just zip up all code files in our project, plus the Node/npm package.json and/or Python/pip requirements.txt...

...and upload them to a Cloud Storage bucket:

let bucket = "your-bucket-name";
let key = "path/to/upload";

gapi.client.request({
 path: `/upload/storage/v1/b/${bucket}/o`,
 method: "POST",
 params: {
  uploadType: "media",
  name: key
 },
 headers: {
  "Content-Type": "application/zip",
  "Content-Encoding": "base64"
 },
 body: btoa(data)
})).then(res => {
 console.debug("GS upload successful", res);

 return {
  Bucket: res.result.bucket,
  Key: res.result.name
 };
});

Now we can add the Cloud Storage object path into our Deployment Manager template right away!

...
{
 "name": "goofunc",
 "type": "cloudfunctions.v1beta2.function",
 "properties": {
  "function": "goofunc",
  "sourceArchiveUrl": "gs://your-bucket-name/path/to/upload",
  "entryPoint": ...
 }
}

So, how fast is it - for real?

  1. jszip runs in-memory and takes just a few millis - as expected.
  2. If it's the first time after the IDE is loaded, the Google APIs JS client library takes a few seconds to load.
  3. After that, it's a single Cloud Storage API call - to upload our teeny tiny zipfile into our designated Cloud Storage bucket sigma-slappforge-{your Cloud Platform project name}-build-artifacts!
  4. If the bucket is not yet available, and the upload fails as a result, we have two more steps - create the bucket and then re-run the upload. This happens only once in a lifetime.

So for a routine serverless developer, skipping steps 2 and 4, the whole process takes around just one second - the faster your network, the faster it all is!

In comparison to AWS builds, where we want to first run a dependency sync and then a build (each of which is preceded by HTTP OPTIONS requests, thanks to CORS restrictions); this is lightning fast!

(And yeah, this is one of those places where the googleapis client library shines; high above aws-sdk.)

Enough reading - let's roll!

I am a Google Cloud fan by nature - perhaps because my "online" life started with Gmail, and my "cloud dev" life started with Google Apps Script and App Engine. So I'm certainly at bias here.

Still, when you really think about it, Google Cloud is way simpler far more organized than AWS. While this could be a disadvantage when it comes to advanced serverless apps - say, "how do I trigger my Cloud Function periodically?" - GCF is pretty simple, easy and fast. Very much so, when all you need is a serverless HTTP endpoint (webhook) or bucket/queue consumer up and running in a few minutes.

And, when you do that with Sigma IDE, that few minutes could even drop down to a matter of seconds - thanks to the brand new quick build!

So, why waste time reading this - when you can just go and do it right away?!

AS2 Gateway REST API - B2B EDI Exchange Streamlined!

AS2 Gateway REST API: seamless B2B trading integration (courtesy of Pexels)

All right. You have set up your local stations and trading partners on AS2 Gateway B2B Trading Platform - and successfully sent and received messages. Today, let's see how we can automate these AS2 communications - using the brand new AS2 Gateway REST API!

Of course, if you haven't set up AS2 communications yet, you can always get started right away, for free.

I'll be using curl, so I can show you the whole story - URL, headers, payload formats and all. But you can use any tool (say, Postman) to follow along; and use whatever language or library (HttpClient, requests, axios - to name a few) when it comes to programmatic automation!

Authorization

AS2 Gateway REST API is secured with token-based auth, so the first thing you need is an auth token.

curl -XPOST https://api.as2gateway.com/v1/authorize \
    -H 'Content-Type: application/json' \
    --data '{"username":"email@domain.com","password":"pazzword"}'

Replace email@domain.com and pazzword with your actual AS2G account credentials.

The response (formatted for clarity) would be like:

{
  "token": "hereGoesTheLongLongLongLongToken",
  "expiry": 1574299310779
}

Extract out the token field. This would be valid until the time indicated by the expiry timestamp - usually 24 hours. You can reuse the token until then, saving yourself the trouble of running auth before every API call.

Send a Message!

Good! We have the token, so let's send our message!

curl -v -XPOST https://api.as2gateway.com/v1/messages/AWSMSTTN/AWSMPTNR \
    -H 'Authorization: hereGoesTheLongLongLongLongToken' \
    -H 'Content-Type: multipart/form-data; boundary=----foobarbaz' \
    --data-binary \
'------foobarbaz
Content-Disposition: form-data; name="file"; filename="file1.txt"
Content-Type: text/plain

one
------foobarbaz
Content-Disposition: form-data; name="file"; filename="file2.txt"
Content-Type: text/plain

two
------foobarbaz--'

True, this is a bit too much for curl; obviously you would use a more convenient way - File option of Postman, MultipartEntityBuilder for Apache HttpClient, and so on. But the bottom line is:

  • you specify the AS2 IDs of your origin (local trading station) and destination (remote trading partner) as path parameters - on the API call URL
  • you can optionally include a message subject as a query parameter
  • as the request body (payload) you compose and send a multipart request - including the boundary as a Content-Type HTTP header on the API call request
  • you can include multiple files (attachments) in your AS2 message by including as many "file" parts needed, in the multipart body; each with a unique filename attribute.

Looking at the -v trace:

> POST /v1/messages/AWSMSTTN/AWSMPTNR HTTP/1.1
> Host: api.as2gateway.com
> User-Agent: curl/7.47.0
> Accept: */*
> Authorization: hereGoesTheLongLongLongLongToken
> Content-Type: multipart/form-data; boundary=----foobarbaz
> Content-Length: 243
>

* upload completely sent off: 243 out of 243 bytes

< HTTP/1.1 202 Accepted
< Date: Wed, 20 Nov 2019 02:53:25 GMT
...
< Link: https://api.as2gateway.com/v1/messages/sent/<0123456789.012.1574218406002@as2gateway.com>
< Content-Type: application/json
< Transfer-Encoding: chunked
<

* Connection #0 to host api.as2gateway.com left intact

{"message":"Successfully added a new outbound entry"}

The response would be an HTTP 202 (Accepted), with:

{"message":"Successfully added a new outbound entry"}

Remember to save up the response HTTP headers too: we're gonna need them later.

Cool! So our AS2 message was sent out?

No. Not yet.

"Queued", "Sent", and "Failed"

When AS2G sent you that 202 response, it meant that it has accepted the challenge of sending your files to AWSMPTNR. So the message is now enqueued for delivery.

Within about 5 seconds, AS2G will attempt to send your message out to AWSMPTNR, based on the URL, certificates, etc. that you have configured on their partner configuration.

(If the send fails, it will automatically retry in 10s; then 20s; then 40s; and so forth, up to 10 times.)

So, after you call the message send API, the message could end up in one of three places:

  • If AS2G has not yet tried to send the message - or has tried, failed and is planning to try again - it would appear under Queued.
  • If AS2G successfully sends it out, it would appear under Sent.
  • If the send fails, it will go under Failed. Also, as mentioned before, depending on the nature of the failure (e.g. if it is retryable - such as a temporary network issue) it may continue to appear under Queued as well.

So, in order to check what happened to the message, we better check under Sent first - we all love happy-day scenarios, don't we?

Checking Under Sent

curl 'https://api.as2gateway.com/v1/messages/sent/<0123456789.012.1574218406002@as2gateway.com>' \
    -H 'Authorization: hereGoesTheLongLongLongLongToken'

Looks fine: GET the message under sent, with that particular ID. But how do you get the ID?

Remember the response headers we saved up from the send API call? In there, there is a Link header, containing not just the message ID - but the entire sent-message URL!

Link: https://api.as2gateway.com/v1/messages/sent/<0123456789.012.1574218406002@as2gateway.com>

You can directly call that URL (adding the auth header) and get back an AS2 Message entity representing the sent-out message:

{
  "as2MessageId": "<0123456789.012.1574218406002@as2gateway.com>",
  "persistedTimestamp": 1574218406002,
  "compressed": true,
  "encrypted": true,
  "signed": true,
  "subject": "AWSMPTNR",
  "receiverId": "AWSMPTNR",
  "senderId": "AWSMSTTN",
  "transportStatusReceived": 200,
  "deliveryStatus": "Delivered",
  "mdnStatus": "Received",
  "partnerType": "Production",
  "mdnMessage": {
    "persistedTimestamp": 1574218406100,
    "mdnError": false,
    "content": "MDN for Message-ID: <0123456789.012.1574218406002@as2gateway.com>\r\n\
From: AWSMSTTN\r\nTo: AWSMPTNR\r\nReceived on: Wed Nov 20 02:53:26 UTC 2019\r\n\
Status: processed\r\n\
Comment: This is not a guarantee that the message has been completely processed or \
understood by the receiving translator\r\n\
Powered by the AdroitLogic UltraESB-X (https://www.adroitlogic.com)\r\n"
  },
  "attachments": [
    {
      "name": "file1.txt",
      "size": 3
    },
    {
      "name": "file2.txt",
      "size": 3
    }
  ]
}
  • If the message got an MDN back, it would also be included - under the mdnMessage field.
  • Also, the transportStatusReceived field represents the actual HTTP response code AS2G got back when sending out the message. It may become handy, say, when troubleshooting a missing MDN - knowing what we got back from the actual send-out HTTP call.

In this case, transportStatusReceived is HTTP 200 - and we have successfully received a non-error MDN ("mdnError": false); voilà!

Checking Under Failed

If your message was not so lucky - i.e. it failed to get sent - a copy would end up under failed:

curl 'https://api.as2gateway.com/v1/messages/failed/<0123456789.012.1574218406002@as2gateway.com>' \
    -H 'Authorization: hereGoesTheLongLongLongLongToken'

{
  "as2MessageId": "<0123456789.012.1574218406002@as2gateway.com>",
  "persistedTimestamp": 1574218406002,
  "compressed": true,
  "encrypted": true,
  "signed": true,
  "subject": "AWSMPTNR",
  "receiverId": "AWSMPTNR",
  "senderId": "AWSMSTTN",
  "transportStatusReceived": 404,
  "deliveryStatus": "Not Delivered",
  "mdnStatus": "Pending",
  "partnerType": "Production",
  "attachments": [
    {
      "name": "file1.txt",
      "size": 3
    },
    {
      "name": "file2.txt",
      "size": 3
    }
  ]
}

Almost same as Sent, except for the path fragment change. You can derive the URL easily from the Link header described above.

Note that some messages fail one-time, but others can keep on retrying and failing - in the latter case, there could be multiple entries under failed with the same message ID; but the API will always return the most recent one.

Checking Under Queued

If the message doesn't appear under either of the above - say, after 10 seconds from the send call - AS2G might not have gotten a chance to try to send it out. If so, it could be still in the queue:

curl 'https://api.as2gateway.com/v1/messages/queued/<0123456789.012.1574218406002@as2gateway.com>' \
    -H 'Authorization: hereGoesTheLongLongLongLongToken'

{
  "as2MessageId": "<0123456789.012.1574218406002@as2gateway.com>",
  "persistedTimestamp": 1574218406002,
  "compressed": true,
  "encrypted": true,
  "signed": true,
  "subject": "AWSMPTNR",
  "receiverId": "AWSMPTNR",
  "senderId": "AWSMSTTN",
  "partnerType": "Production"
}

Note that, while you checked sent and failed and decided to check queued, AS2G may have started to send the message. In that case it may not appear under queued as well - because we don't currently expose "retry-in-progress" messages via the queued endpoint.

Bottom Line: Checking Sent-out Message Status

  1. Call the send API.
  2. Give AS2G a few seconds - ideally 10-20 seconds - so it can try to send the message once.
  3. Check sent (Link header from send call). If the message is there, all went well.
  4. If message is not in sent, check failed. If it is there, the send-out has failed.
  5. If both endpoints don't have the message, AS2G is probably busy (it has not had a chance to process your message).
    1. Check queued, after a few seconds. If you see the message, it has probably failed once - and an entry should now be there under failed.
    2. If queued doesn't have the message, check sent and failed again - just to make sure.
    3. If it is still missing, repeat the queued-sent-failed check after a few seconds' delay.
    4. Unlikely but possible (if you are really unlucky): if the message remains missing after several such tries, something has gone wrong in AS2G itself; shout out for help.

There's More!

Cool! Now you can send out AS2 messages via the AS2G REST API - and ensure that it actually got sent out, check the received MDN, and so forth.

But the API goes way beyond that:

  • download actual MDN for a sent message
  • stop/resume retrying of a queued message
  • check received messages: GET /messages/received - with several filters
  • download attachments of a received message
  • mark/unmark a received message as "read" - so you won't see it again in the received messages listings
  • list sent, queued and failed messages - similar to /received

We are constantly working on improving the API - so, by the time you read this, there may be a lot of other cool features in the API as well; check our API docs for the complete and latest updates!

In Closing

AS2 Gateway makes it pretty easy to manage your B2B document exchange.

So far AS2G supported SFTP - and custom mechanisms for the On-Premise version - as integration points.

But now, with the REST API, it all becomes way much easier!

Right now, the REST API is available for all AS2G customers, at no extra cost.

But the best news is, it is fully available - along with all the other premium features - during the 30-day, extensible free trial!

So check it out, try it out, and tell us what you think!

Good luck with all your B2B communications!