13
votes

Im working on an existing Windows Service project in VS 2013.

I've added a web API Controller class I cant remember now if its a (v2.1) or (v1) controller class....Anyway I've called it SyncPersonnelViaAwsApiController

Im trying to call it from a AWS lambda...so If I call the GET

public string Get(int id)
    {
        return "value";
    }

with const req = https.request('https://actualUrlAddress/api/SyncPersonnelViaAwsApi/Get/4', (res) => {

I get returned body: undefined"value" which is correct. However if I try and call

const req = https.request('https://actualUrlAddress/api/SyncPersonnelViaAwsApi/SapCall', (res) => {

I get returned body: undefined{"Message":"The requested resource does not support http method 'GET'."}

 //// POST api/<controller>
    public string SapCall([FromBody]string xmlFile)
    {
        string responseMsg = "Failed Import User";

        if (!IsNewestVersionOfXMLFile(xmlFile))
        {
            responseMsg = "Not latest version of file, update not performed";
        }
        else
        {
            Business.PersonnelReplicate personnelReplicate = BusinessLogic.SynchronisePersonnel.BuildFromDataContractXml<Business.PersonnelReplicate>(xmlFile);
            bool result = Service.Personnel.SynchroniseCache(personnelReplicate);

            if (result)
            {
                responseMsg = "Success Import Sap Cache User";
            }
        }

        return "{\"response\" : \" " + responseMsg + " \" , \"isNewActiveDirectoryUser\" : \" false \"}";
    }

Does anyone have any idea why it works for GET and not POST?

As we can hit the get im confident its not the lambda but I have included it just incase

const AWS = require('aws-sdk');
const https = require('https');
var s3 = new AWS.S3();
var un;
var pw;
var seralizedXmlFile;


let index = function index(event, context, callback) {

    // For the purpose of testing I have populated the bucket and key params with objects that already exist in the S3 bucket  
    var params = {
    Bucket: "testbucketthur7thdec",
    Key: "personnelData_50312474_636403151354943757.xml"
};


// Get Object from S3 bucket and add to 'seralizedXmlFile'
s3.getObject(params, function (data, err) {
    console.log("get object from S3 bucket");
    if (err) {
        // an error occurred
    }
    else
    {
        console.log("data " + data);
        // populate seralizedXmlFile with data from S3 bucket
        let seralizedXmlFile = err.Body.toString('utf-8'); // Use the encoding necessary
        console.log("objectData " + seralizedXmlFile);
    }

});

    // set params
    var ssm = new AWS.SSM({ region: 'Usa2' });
    console.log('Instatiated SSM');
    var paramsx = {
        'Names': ['/Sap/ServiceUsername', '/Sap/ServicePassword'],
        'WithDecryption': true
    };

// password and username
    ssm.getParameters(paramsx, function (err, data) {
        console.log('Getting parameter');
        if (err) console.log(err, err.stack); // an error occurred
        else {
            console.log('data: ' + JSON.stringify(data));           // successful response
            console.log('password: ' + data.Parameters[0].Value);
            console.log('username: ' + data.Parameters[1].Value);
            pw = data.Parameters[0].Value;
            un = data.Parameters[1].Value;
        }


        // request to external api application & remove dependency on ssl
        process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

        //POST DOES NOT WORK
        const req = https.request('https://actualUrlAddress/api/SyncPersonnelViaAwsApi/SapEaiCall', (res) => {
        //GET WORKS
       // const req = https.request('https://actualUrlAddress/api/SyncPersonnelViaAwsApi/Get/4', (res) => {

            res.headers + 'Authorization: Basic ' + un + ':' + pw;
            let body = seralizedXmlFile;
            console.log('seralizedXmlFile: ' + seralizedXmlFile); 
            console.log('Status:', res.statusCode);
            console.log('Headers:', JSON.stringify(res.headers));

            res.setEncoding('utf8');
            res.on('data', (chunk) => body += chunk);
            res.on('end', () => {
                console.log('Successfully processed HTTPS response');
                callback(null, body);
                console.log('returned body:', body);

            });
        });
        req.end();
    });
};
exports.handler = index;

UPDATE Thanks to @Thangadurai post with AWS Lambda - NodeJS POST request and asynch write/read file

I was able to include a post_options...please see updated lambda

          // An object of options to indicate where to post to
    var post_options = {
        host: 'https://actualUrlAddress',
        port: '80',
        path: '/api/SyncPersonnelViaAwsApi/SapEaiCall',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': post_data.length
        }
    };

 const req = https.request(post_options, (res) => {
   res.headers + 'Authorization: Basic ' + un + ':' + pw;
            let body = seralizedXmlFile;
            console.log('seralizedXmlFile: ' + seralizedXmlFile); 
            console.log('Status:', res.statusCode);
            console.log('Headers:', JSON.stringify(res.headers));

            res.setEncoding('utf8');
            res.on('data', (chunk) => body += chunk);
            res.on('end', () => {
                console.log('Successfully processed HTTPS response');
                callback(null, body);
                console.log('returned body:', body);

            });
        });
        req.end();

It is now flagging as error:

Error: getaddrinfo ENOTFOUND http://actualUrlAddress http://actualUrlAddress.private:80

I had this getaggrinfo ENOTFOUND error before, it means it cant find the address....but arnt the hostname and api path correct?

I am trying to reach

const req = https.request('https://actualUrlAddress/api/SyncPersonnelViaAwsApi/SapCall

and yes the port is 80

any help would be appreciated Ta M

2
The error says that /GET is unsupported, not that /POST is unsupported.Aluan Haddad
From your description and code, GET works because the API method https://actualUrlAddress/api/SyncPersonnelViaAwsApi/Get/4 is designed to accept HTTP GET request. But the other API method SapCall supports only HTTP POST verb, I am assuming this because a comment in the API method definition says so, and the [FromBody] attribute also indicates that. [Note: HTTP GET request will not have a body.Thangadurai
@AluanHaddad thank yo for reply, I know what the error says but that is the rror I see when I try to call POSTJohn
@Thangadurai thank you for reply, what are you suggesting that the body is empty?John
@John I think you aren't sending a post request.Aluan Haddad

2 Answers

9
votes

Skipping right to the update part (everything else is not relevant as I understand). Options should look like this:

var post_options = {
    host: 'actualUrlAddress',
    protocol: 'https:'
    port: '443',
    path: '/api/SyncPersonnelViaAwsApi/SapEaiCall',
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Content-Length': post_data.length
    }
};

Since as documentation states, host and protocol are in two separate properties, and SSL port is very unlikely to be 80, usually it is 443.

4
votes

Given that Web API is being used the assumption is that the default routeing has also been configured.

I would suggest enabling attribute routing

Reference Attribute Routing in ASP.NET Web API 2

public static class WebApiConfig {
    public static void Register(HttpConfiguration config) {
        // Attribute routing.
        config.MapHttpAttributeRoutes();

        // Convention-based routing.
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

and updating the controller with the appropriate attributes and syntax

[RoutePrefix("api/SyncPersonnelViaAwsApi")]
public class SyncPersonnelViaAwsApiController : ApiController {

    //GET api/SyncPersonnelViaAwsApi/4
    [HttpGet]
    [Route("{id:int}")]
    public IHttpActionResult Get(int id) {
        return Ok("value");
    }

    //POST api/SyncPersonnelViaAwsApi
    [HttpPost]
    [Route("")]
    public IHttpActionResult SapCall([FromBody]string xmlFile) {
        string responseMsg = "Failed Import User";

        if (!IsNewestVersionOfXMLFile(xmlFile)) {
            responseMsg = "Not latest version of file, update not performed";
        } else {
            Business.PersonnelReplicate personnelReplicate = BusinessLogic.SynchronisePersonnel.BuildFromDataContractXml<Business.PersonnelReplicate>(xmlFile);
            bool result = Service.Personnel.SynchroniseCache(personnelReplicate);
            if (result) {
                responseMsg = "Success Import Sap Cache User";
            }
        }

        var data = new { 
            response = responseMsg,
            isNewActiveDirectoryUser = false
        };

        Ok(data);
    }
}

Take note of the expected paths in the comments above the actions.

The [Http{Verb}] attributes tell the routing framework what requests can be mapped to the action.

now from the client the paths to call would be

GET api/SyncPersonnelViaAwsApi/4
POST api/SyncPersonnelViaAwsApi

As already mentioned in the comments, the port 80 is not used for HTTP calls. Even the linked example in the updated question uses port 443 for HTTPS calls. The header in options show JSON yet the body infers that XML is being sent.

var post_options = {
    host: 'actualUrlAddress',
    protocol: 'https',
    port: '443',
    path: '/api/SyncPersonnelViaAwsApi',
    method: 'POST',
    headers: {
        'Content-Type': 'application/xml',
        'Content-Length': post_data.length
    }
};

In order for the client to successfully communicate with the Web API the client will need to make sure it is making a valid request.