Luís Peres
Luís Peres

Reputation: 89

Azure Blob Service REST API - Error: read ECONNRESET

The goal and where I got so far

I'm trying to use the Azurue Storage Blob Services REST API with Node.JS. So far I managed to make successful requests for the List Containers and Get Blob Services Properties operations. Now I'm trying Put Blob based on this documentation from MS.

I've had 400 and 403 errors and searched for similar questions, even when it was a question on C# or on R instead of Node.JS, reading it helped me understand what I might be doing wrong and change my code. In that case, it was the signature and its Canonicalized Resource and Canonicalized Headers that lack clearer documentation.

The problem

When I thought I fixed the signature problem (after all the response stopped telling me that that was the problem) and now I don't have those errors anymore, all that happens is: I make the request and it freezes for a while; after some time I receive the message:

events.js:292
      throw er; // Unhandled 'error' event
      ^

Error: read ECONNRESET
    at TLSWrap.onStreamRead (internal/stream_base_commons.js:205:27)
Emitted 'error' event on ClientRequest instance at:
    at TLSSocket.socketErrorListener (_http_client.js:426:9)
    at TLSSocket.emit (events.js:315:20)
    at emitErrorNT (internal/streams/destroy.js:92:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
    at processTicksAndRejections (internal/process/task_queues.js:84:21) {
  errno: 'ECONNRESET',
  code: 'ECONNRESET',
  syscall: 'read'
}

From this question on a similar issue I learned that it's an interrupted connection on the other side of the TCP. So it's probably not the signature anymore but I have no idea what it is. Do I need to make another type of request before a Put Blob?

I tried changing the signature, also tried creating a a local .txt file to be uploaded, and tried uploading a simple string from a variable. I'm not sure where the data should come from to be uploaded.

I think my main problem is that for the other errors and issues I've had, I was given some information to (eventually and after a lot of reading) solve it; but now I don't even get a Status 200 anymore.

The code

My function to create a signature:

/**
   * Authorization using HMAC SHA 256.
   * @param {String} VERB - Request method to be used (GET, PUT).
   * @param {String} strTime - Time of the request, in RFC 1123 Format.
   * @param {String} path - Path of the URL, containing the name of the
   * container and the query parameters.
   */
  create_signature(VERB, strTime, uri, content) {
    VERB = VERB.toUpperCase();

    // removing first slash
    uri = uri.replace("/","");
    
    // separating '/container/blob?q=query&q=query' into 'container/blob' and 'q=query&q=query'
    var [path, query] = uri.split("?");
    // changing 'q=query&q=query' to 'q:query\nq:query' if '?' is included
    query = query ? query.replace(/\=/g,":").replace(/\&/g,"\n") : '';
    // without the '?' char the separation is '/container/blob' and ''
    
    const content_type = "text/plain; charset=UTF-8";
    const content_length = content.length.toString();
    
    let strToSign = VERB + "\n" + // VERB
      "\n" +                      // Content-Encoding
      "\n" +                      // Content-Language
      content_length + "\n" +     // Content-Length
      "\n" +                      // Content-MD5
      content_type + "\n" +         // Content-Type
      "\n" +                      // Date
      "\n" +                      // If-Modified-Since
      "\n" +                      // If-Match
      "\n" +                      // If-None-Match
      "\n" +                      // If-Unmodified-Since
      "\n" +                      // Range
      // CanonicalizedHeaders
      `x-ms-blob-type:BlockBlob` + "\n" +
      `x-ms-date:${strTime}` + "\n" + 
      `x-ms-version:${this.version}` + "\n" +
      // CanonicalizedResource
      `/${this.account_name}/${path}`;

    console.log(strToSign);
    // strToSign = strToSign.toLowerCase();
    // strToSign = encodeURIComponent(strToSign);

    // generating secret from account key
    var secret = CryptoJS.enc.Base64.parse(this.account_key);
    // encrypting the signature
    var hash = CryptoJS.HmacSHA256(strToSign, secret);
    var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
    var auth_sig = `SharedKey ${this.account_name}:` + hashInBase64;

    return auth_sig;
  }

To make a request using the http module:

// making the request using the http module:
  put_blob(container_name, filename) {
    const time_UTC_str = new Date().toUTCString();
    var path = `/${container_name}/${filename}`;

    const obj = "hello world";

    const signature = this.create_signature('PUT', time_UTC_str, path, obj);


    const req_params = {
      method: 'PUT',
      hostname: this.hostname,
      path: path,
      headers: {
        'Authorization': signature,
        'x-ms-date': time_UTC_str,
        'x-ms-version': this.version,
        'x-ms-blob-type': 'BlockBlob',
        'Content-Length': obj.length.toString(),
        'Content-Type': "text/plain; charset=UTF-8"
      }
    }

    let req = http.request(req_params, this.res_handler);
    req.end();
  }

The response handler function:

  /**
 * Callback function that handles and parses the responses, 
 * including how the search results will be processed.
 * @param {object} res - response from request
 */
  res_handler = function (res) {

    let body = '';
    // storing body
    res.on('data', (dat) => {
      body += dat;
    });

    // when signaling 'end' flag
    res.on('end', () => {
      // parsing response
      if (!res.complete) {
        console.error('The connection was terminated while the message was still being sent');
      } else {
        // console.log(res);
        console.log(`Status: ${res.statusCode} - ${res.statusMessage}`);
      }
      console.log('Response: ' + body);
      
    });

    // handling errors
    res.on('error', (err) => {
      console.error(`Error ${err.statusCode}: ${err.statusMessage}`);
    });

  };

PS: Should I try another cloud service or are they all complicated and bad documented?

Upvotes: 3

Views: 2545

Answers (1)

Luís Peres
Luís Peres

Reputation: 89

I had forgotten to use req.write() to actually write the data. I'm embarrassed but I will explain what happenned in case someone has a similar problem.

As I explained before, that error comes from an interruption on the connection from the other side, like a timeout for inactivity. So it kept waiting for me to write data to the request, and because I didn't, it eventually stopped waiting, thus returning that error.

Turns out it had nothing to do with Azure or Blob Services, I just didn't send anything to be uploaded. And it took me some time to realize that but in the process I really learned a lot by researching about this.

The last lines of the put_blob() method now look like this:

let req = http.request(req_params, this.res_handler);
req.write(obj);    // it was missing this! 
req.end();

Now I (hopefully) will never forget to write data on a PUT request.

Upvotes: 3

Related Questions