Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PATCH Request not passing Body #247

Open
laurentades opened this issue Mar 12, 2018 · 16 comments
Open

PATCH Request not passing Body #247

laurentades opened this issue Mar 12, 2018 · 16 comments

Comments

@laurentades
Copy link

Hi

I am using the ClientRequest to create a request to a REST API
The body goes through with:
httpRequest.write(from: httpBody)

When using httpMethod PATCH, the body string is not passed across
When using httpMethod PUT or POST, the body string goes through...

@ianpartridge
Copy link
Collaborator

Hmm it looks like we do set up the curl handle to upload on PATCH: see https://github.com/IBM-Swift/Kitura-net/blob/master/Sources/KituraNet/ClientRequest.swift#L419

Can you provide some example code showing the problem?

@laurentades
Copy link
Author

See code below

No issue when
httpMethod = "POST"
or
httpMethod = "PUT"

However when
httpMethod = "PATCH"
The body does not come through

I have to confess that being very new to all this my code is very made up from posts all around GitHub, StackOverflow and other blogs... I would not be fussed that you find something blatantly wrong in there.... :-S

Thanks

`
import SwiftyJSON
import KituraNet
func httpCall(httpUri: String, httpMethod: String, httpBody: String, queryDict: [String:String], httpHeaders: [String:String]) -> [String:Any] {

	var returnDict = [String:Any]()
	var queryString: String = ""
		var i: Int = 0
		for (key, value) in queryDict{
			i+=1
			queryString = queryString + key + "=" + value
			if i != queryDict.count {
				queryString = queryString + "&"
			}
		}
	let httpQueryEncoded = queryString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)

	var httpRequestOptions:[ClientRequest.Options] = [
		.schema(""),
		.method(httpMethod),
		.hostname(httpUri + "?" + httpQueryEncoded!),
		.headers(httpHeaders)
	]

	var httpStatus: Int = 0
	var httpResp: String = ""

	let httpRequest = HTTP.request(httpRequestOptions) { response in
		do {
			httpResp = try response!.readString()!
			print("httpResp: \(httpResp)")
			httpStatus = try response!.status

			if let jsonDictionary = JSON.parse(string: httpResp).dictionaryObject {
				returnDict = jsonDictionary 
				print("jsonDictionary: \(jsonDictionary)")
			} else {
				returnDict = ["error": "Unknown data format returned"]
			}
		} catch {
			httpResp = "error: http request returned an error"
			//returnDict["seqHalt"] = "true"
		}
	}

	httpRequest.write(from: httpBody)

	httpRequest.end()

	return returnDict
}`

@DunnCoding
Copy link

Hi @laurentades :)

I've been looking into this issue for you, thanks for providing your example code!
I was wondering if you could also share the code where you call this function?

Basically I'm interested to know what parameters you're passing through to your httpCall(...) function :)

Thanks!

@laurentades
Copy link
Author

Hi @ddunn2
Sorry for long awaited reply.

The full code is below:

`/*
*/

import SwiftyJSON
import KituraNet

func httpCall(httpUri: String, httpMethod: String, httpBody: String, queryDict: [String:String], httpHeaders: [String:String]) -> [String:Any] {

var returnDict = [String:Any]()

var queryString: String = ""
	var i: Int = 0
	for (key, value) in queryDict{
		i+=1
		queryString = queryString + key + "=" + value
		if i != queryDict.count {
			queryString = queryString + "&"
		}
	}
print("httpUri: \(httpUri)")
print("queryString: \(queryString)")
print("httpHeaders: \(httpHeaders)")
print("httpBody: \(httpBody)")

let httpQueryEncoded = queryString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)

var httpRequestOptions:[ClientRequest.Options] = [
	.schema(""),
	.method(httpMethod),
	.hostname(httpUri + "?" + httpQueryEncoded!),
	.headers(httpHeaders)
]

var httpStatus: Int = 0
var httpResp: String = ""

let httpRequest = HTTP.request(httpRequestOptions) { response in
	do {
		httpResp = try response!.readString()!
		print("httpResp: \(httpResp)")
		httpStatus = try response!.status

		if let jsonDictionary = JSON.parse(string: httpResp).dictionaryObject {
			returnDict = jsonDictionary // we got back JSON
			print("jsonDictionary: \(jsonDictionary)")
		} else {
			returnDict = ["error": "Unknown data format returned"]
		}

	} catch {
		httpResp = "error: http request returned an error"
		//returnDict["seqHalt"] = "true"
	}
}

httpRequest.write(from: httpBody)

httpRequest.end()

return returnDict

}

func main(args: [String:Any]) -> [String:Any] {

var returnDict = [String:Any]()

let seqHalt: String? = args["seqHalt"] as! String
let passThruData: [String:Any]? = args["passThruData"] as! [String:Any]

if seqHalt == "false" {
	
	var httpResponse = [String:Any]()
	    
    let httpUri: String? = args["httpUri"] as! String // String
    let httpMethod: String? = args["httpMethod"] as! String // String
    let httpBody: String? = args["httpBody"] as!  String // Dictionary
    var httpHeaders: [String:String]? = args["httpHeaders"] as! [String:String]   //Dictionary
    var queryDict: [String:String]? = args["queryDict"] as! [String:String]
    let pagingMode: String? = args["pagingMode"] as! String // String
    let pageSize: String? = args["pageSize"] as! String // String
    let jsonCombinePathString: String? = args["jsonCombinePathString"] as! String // String
    
    let jsonCombinePath = jsonCombinePathString!.components(separatedBy: ",")
    
	if pagingMode! == "none" {
		
		returnDict = httpCall(httpUri: httpUri!, httpMethod: httpMethod!, httpBody: httpBody!, queryDict: queryDict!, httpHeaders: httpHeaders!)
		
	} else {
		
		var loopHalt: String = "false"
		var loopCount: Int = 0
		
		var dataArray = [[String:Any]]()
		
		repeat {
			loopCount += 1
			
			switch pagingMode! {

				case "recordRange":
					queryDict!["fromIndex"] = String((loopCount - 1) * (Int(pageSize!)!) + 1)
					queryDict!["toIndex"] = String(loopCount * Int(pageSize!)!)
					
					httpResponse = httpCall(httpUri: httpUri!, httpMethod: httpMethod!, httpBody: httpBody!, queryDict: queryDict!, httpHeaders: queryDict!)
			
					//print("httpResponse: \(httpResponse)")
                    var httpResponseJson = JSON(httpResponse)
                    let response = httpResponseJson["response"].dictionaryValue
					let responseError = response["error"]
					let responseNoData = response["nodata"]
					let responseResult = response["result"]

					if responseError != nil {
						loopHalt = "true"

					} else if responseNoData != nil {
						loopHalt = "true"

					} else {
					    dataArray = jsonCombine(baseArray: dataArray, addData: httpResponse, jsonCombinePathString: jsonCombinePathString!)
					    
					}
		
				case "pageIndex":
					print("pagingMode: \(pagingMode!)")
				case "offset":
					print("pagingMode: \(pagingMode!)")
				default:
					print("pagingMode: \(pagingMode!)")
			}
		
		} while loopHalt == "false"
		
	}
	
    
}else{
    returnDict["seqHalt"] = "true"
}

returnDict["passThruData"] = passThruData

return returnDict
}`

@laurentades
Copy link
Author

Hi,
Quick update.
Looks lie it is actually the target service acting up with the PATCH request rather than OpenWhisk struggling.
Will confirm further on.

@laurentades
Copy link
Author

Hi,
Eventually this is not something happening on the target API side: trying the patch method via Postman works just fine with the same parameters..
Any idea ?

@DunnCoding
Copy link

Hi @laurentades
Thanks for providing the code and the updates!
I did some testing with this and also confirmed the patch method seems to be working when I setup my own project and passed through some custom parameters.

I'm going to attempt to recreate the problem you're running into using the code you've provided, once I have more information I'll update here as well.

@DunnCoding
Copy link

@laurentades What parameters are you passing to your func main(args: [String:Any]) -> [String:Any] {...} function?

@laurentades
Copy link
Author

Hi, the parameters passed to the main func is basically a json set of values which are extracted and passed to variables pretty much just after the beginning of the function:

httpUri: Base target API url
httpMethod: HTTP method (GET,POST,PUT,PATCH...)
httpBody: request body as a string
httpHeaders: Dictionary formatted set of values later-on processed into a request header
queryDict: Dictionary formatted set of values later-on processed into a request query string
pagingMode : text value used for specific API functionality (irrelevant here)
pageSize : numeric value used for specific API functionality (irrelevant here)
jsonCombinePathString : text value used for specific API functionality (irrelevant here)

Hope that helps.... :-)
Thanks !

@DunnCoding
Copy link

Thanks for that! :)
Do you have the values you're passing in? Mainly I want to know the values you're passing into httpBody, httpHeaders and queryDict, this will allow me to recreate your exact issue :)

@laurentades
Copy link
Author

ah yes sure:
httpHeaders: ["Authorization": "Bearer keyDbQyg9ApRFyffG", "Content-type": "application/json; charset=utf-8", "Accept": "application/json"]

httpBody: {"fields":{"targetGP":0,"channelDiscount":0,"importUplift":0,"brandId":"1804391000001949436","brandName":"Generic Local"},"typecast":true}

In this case queryDict is empty

@DunnCoding
Copy link

DunnCoding commented Apr 13, 2018

Thanks for providing that information!

So I've put together a little test scenario, and just want to confirm we're on the same page.

I created a simple Kitura server using the kitura init command, added all necessary packages and then I edited the postInit() function to look like this:

func postInit() throws {
        // Capabilities
        initializeMetrics(app: self)

        // Endpoints
        initializeHealthRoutes(app: self)
        router.all(middleware: BodyParser())
        router.patch("/") { request, response, next in
            print(request.body)
            response.send("OK")
            next()
        }
    }

I then created a project that includes all the code you've provided and made the necessary call, the code looks like this:

import KituraNet
import SwiftyJSON
import Foundation

func httpCall(httpUri: String, httpMethod: String, httpBody: String, queryDict: [String:String], httpHeaders: [String:String]) -> [String:Any] {
    
    var returnDict = [String:Any]()
    
    var queryString: String = ""
    var i: Int = 0
    for (key, value) in queryDict{
        i+=1
        queryString = queryString + key + "=" + value
        if i != queryDict.count {
            queryString = queryString + "&"
        }
    }
    print("httpUri: \(httpUri)")
    print("queryString: \(queryString)")
    print("httpHeaders: \(httpHeaders)")
    print("httpBody: \(httpBody)")
    
    let httpQueryEncoded = queryString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
    
    var httpRequestOptions:[ClientRequest.Options] = [
        .schema(""),
        .method(httpMethod),
        .hostname(httpUri + "?" + httpQueryEncoded!),
        .headers(httpHeaders)
    ]
    
    var httpStatus: Int = 0
    var httpResp: String = ""
    
    let httpRequest = HTTP.request(httpRequestOptions) { response in
        do {
            httpResp = try response!.readString()!
            print("httpResp: \(httpResp)")
            httpStatus = try response!.status
            
            if let jsonDictionary = JSON.parse(string: httpResp).dictionaryObject {
                returnDict = jsonDictionary // we got back JSON
                print("jsonDictionary: \(jsonDictionary)")
            } else {
                returnDict = ["error": "Unknown data format returned"]
            }
            
        } catch {
            httpResp = "error: http request returned an error"
            //returnDict["seqHalt"] = "true"
        }
    }
    
    httpRequest.write(from: httpBody)
    
    httpRequest.end()
    
    return returnDict
}


func main(args: [String:Any]) -> [String:Any] {
    
    var returnDict = [String:Any]()
    
    let seqHalt: String? = args["seqHalt"] as! String
    let passThruData: [String:Any]? = args["passThruData"] as! [String:Any]
    
    if seqHalt == "false" {
        
        var httpResponse = [String:Any]()
        
        let httpUri: String? = args["httpUri"] as! String // String
        let httpMethod: String? = args["httpMethod"] as! String // String
        let httpBody: String? = args["httpBody"] as!  String // Dictionary
        var httpHeaders: [String:String]? = args["httpHeaders"] as! [String:String]   //Dictionary
        var queryDict: [String:String]? = args["queryDict"] as! [String:String]
        let pagingMode: String? = args["pagingMode"] as! String // String
        let pageSize: String? = args["pageSize"] as! String // String
        let jsonCombinePathString: String? = args["jsonCombinePathString"] as! String // String
        
        let jsonCombinePath = jsonCombinePathString!.components(separatedBy: ",")
        
        if pagingMode! == "none" {
            
            returnDict = httpCall(httpUri: httpUri!, httpMethod: httpMethod!, httpBody: httpBody!, queryDict: queryDict!, httpHeaders: httpHeaders!)
            
        } else {
            
            var loopHalt: String = "false"
            var loopCount: Int = 0
            
            var dataArray = [[String:Any]]()
            
            repeat {
                loopCount += 1
                
                switch pagingMode! {
                    
                case "recordRange":
                    queryDict!["fromIndex"] = String((loopCount - 1) * (Int(pageSize!)!) + 1)
                    queryDict!["toIndex"] = String(loopCount * Int(pageSize!)!)
                    
                    httpResponse = httpCall(httpUri: httpUri!, httpMethod: httpMethod!, httpBody: httpBody!, queryDict: queryDict!, httpHeaders: queryDict!)
                    
                    //print("httpResponse: \(httpResponse)")
                    var httpResponseJson = JSON(httpResponse)
                    let response = httpResponseJson["response"].dictionaryValue
                    let responseError = response["error"]
                    let responseNoData = response["nodata"]
                    let responseResult = response["result"]
                    
                    if responseError != nil {
                        loopHalt = "true"
                        
                    } else if responseNoData != nil {
                        loopHalt = "true"
                        
                    } else {
//    I commented this line out as I assume jsonCombine is a custom function you've created
//                        dataArray = jsonCombine(baseArray: dataArray, addData: httpResponse, jsonCombinePathString: jsonCombinePathString!)
                        
                    }
                    
                case "pageIndex":
                    print("pagingMode: \(pagingMode!)")
                case "offset":
                    print("pagingMode: \(pagingMode!)")
                default:
                    print("pagingMode: \(pagingMode!)")
                }
                
            } while loopHalt == "false"
            
        }
        
        
    }else{
        returnDict["seqHalt"] = "true"
    }
    returnDict["passThruData"] = passThruData
    
    return returnDict
}


let result = main(args: ["httpUri": "http://localhost:8080",
            "httpMethod": "PATCH",
            "httpBody": "{\"fields\":{\"targetGP\":0,\"channelDiscount\":0,\"importUplift\":0,\"brandId\":\"1804391000001949436\",\"brandName\":\"Generic Local\"},\"typecast\":true}",
            "queryDict": [:],
            "httpHeaders": ["Authorization": "Bearer keyDbQyg9ApRFyffG", "Content-type": "application/json; charset=utf-8", "Accept": "application/json"],
            "seqHalt": "false",
            "pagingMode": "none",
            "passThruData": ["": ""],
            "pageSize": "",
            "jsonCombinePathString": ""
            ])

print(result)

When I start up the server and run the code, I get this output on the server side:

Optional(Kitura.ParsedBody.json(["fields": {
    brandId = 1804391000001949436;
    brandName = "Generic Local";
    channelDiscount = 0;
    importUplift = 0;
    targetGP = 0;
}, "typecast": 1]))

To me this suggests the body is being passed through? Is this the issue you're referring to? Or have I made a mistake in my recreation?

@laurentades
Copy link
Author

Hi,
What you are doing seems right. However that does not match what i am experiencing. As i wante to make sure the target API is no the issue, I use a generic request bin service to check data stream.

Here is that comes into this bin for the PUT request:

http://requestbin.fullcontact.com
PUT /11njq5e1  application/json; charset=utf-8
 132 bytes 7s ago  
From 158.175.125.86, 52.46.38.68
FORM/POST PARAMETERS
None
HEADERS
Cloudfront-Is-Tablet-Viewer: false

Connect-Time: 0

Host: requestbin.fullcontact.com

Total-Route-Time: 0

Content-Length: 132

Authorization: Bearer keyDbQyg9ApRFyffG

Cloudfront-Forwarded-Proto: http

X-Amz-Cf-Id: NYikN7fDMNoxRt9K6Ru7VU_mxWY5Eav1IdY1IuOIcoFCELbQxMq21Q==

Cloudfront-Viewer-Country: US

Cloudfront-Is-Smarttv-Viewer: false

Cloudfront-Is-Mobile-Viewer: false

Cloudfront-Is-Desktop-Viewer: true

X-Request-Id: d6ca222d-b816-41ba-b58c-2eb72fb9abbe

Via: 1.1 b8c7ca6d200a4b87d5b25c2d6f760752.cloudfront.net (CloudFront), 1.1 vegur

Accept: application/json

Connection: close

Content-Type: application/json; charset=utf-8

RAW BODY
{"fields":{"importUplift":0,"channelDiscount":0,"targetGP":-12,"brandName":"Adobe","brandId":"1804391000000697017"},"typecast":true}

and the PATCH request:

http://requestbin.fullcontact.com
PATCH /11njq5e1  application/json; charset=utf-8
 0 bytes 1m ago  
From 158.175.125.86, 52.46.38.43
FORM/POST PARAMETERS
None
HEADERS
Cloudfront-Is-Tablet-Viewer: false

Connect-Time: 0

Host: requestbin.fullcontact.com

Total-Route-Time: 0

Authorization: Bearer keyDbQyg9ApRFyffG

Cloudfront-Forwarded-Proto: http

Content-Type: application/json; charset=utf-8

X-Amz-Cf-Id: XrxWO0jz5l_bAYzbsqz7rI4eCctYxmxMYBZ2vqReC7tvrah46XNo_Q==

Cloudfront-Is-Smarttv-Viewer: false

Cloudfront-Is-Mobile-Viewer: false

Cloudfront-Is-Desktop-Viewer: true

Content-Length: 0

Via: 1.1 fff10bd55076b11530aa8b26ee2c0b65.cloudfront.net (CloudFront), 1.1 vegur

Accept: application/json

X-Request-Id: 698511af-8546-4593-98dd-419893da5813

Cloudfront-Viewer-Country: US

Connection: close

RAW BODY
None

That is consistent with what i am experiencing in my test environment to my target API.

Does that help ?

@DunnCoding
Copy link

Hi,
Apologies for the delay. Thanks for providing that information it's very helpful!
I'll review what you've provided and see if I can find the cause of the issue.

@DunnCoding
Copy link

Hey!
Can you provide the code for your router.patch(...) method on your server?

Thanks :)

@laurentades
Copy link
Author

errr... no idea what this is :-)
I am just using KituraNet as a 'Import' in IBM Bluemix cloud functions. What I have shared so far is basically all i have written for this function...
Hope that's not a show-stopper...!
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants