I'm having this bizarre problem in which I cannot do a simple POST request to a REST service I do not control based on GraphQL.
The problem is that no matter what I set in the httpMethod property of the URLRequest class, it always uses GET instead.
I have done a few tests to discard some problems. For example, I set up a header in the Request and I can verify that the header is being sent to the server (verified with Charles Proxy).
This is the code you can paste and run in a Playground:
import PlaygroundSupport
import Foundation
PlaygroundPage.current.needsIndefiniteExecution = true
let url = URL(string: "http://graphql.anilist.co/")!
let internalSession = URLSession(configuration: .default)
var request = URLRequest(url: url)
request.httpMethod = "POST"
let headers = ["content-type": "application/json"]
request.allHTTPHeaderFields = headers
request.httpBody =
"""
{"query":"query (\n\t$season: MediaSeason,\n\t$year: Int,\n\t$format: MediaFormat,\n\t$excludeFormat: MediaFormat,\n\t$status: MediaStatus,\n\t$minEpisodes: Int,\n\t$page: Int,\n){\n\tPage(page: $page) {\n\t\tpageInfo {\n\t\t\thasNextPage\n\t\t\ttotal\n\t\t}\n\t\tmedia(\n\t\t\tseason: $season\n\t\t\tseasonYear: $year\n\t\t\tformat: $format,\n\t\t\tformat_not: $excludeFormat,\n\t\t\tstatus: $status,\n\t\t\tepisodes_greater: $minEpisodes,\n\t\t\tisAdult: false,\n\t\t\ttype: ANIME,\n\t\t\tsort: TITLE_ROMAJI,\n\t\t) {\n\t\t\t\nid\nidMal\ntitle {\n\tromaji\n\tnative\n\tenglish\n}\nstartDate {\n\tyear\n\tmonth\n\tday\n}\nendDate {\n\tyear\n\tmonth\n\tday\n}\nstatus\nseason\nformat\ngenres\nsynonyms\nduration\npopularity\nepisodes\nsource(version: 2)\ncountryOfOrigin\nhashtag\naverageScore\nsiteUrl\ndescription\nbannerImage\ncoverImage {\n\textraLarge\n\tcolor\n}\ntrailer {\n\tid\n\tsite\n\tthumbnail\n}\nexternalLinks {\n\tsite\n\turl\n}\nrankings {\n\trank\n\ttype\n\tseason\n\tallTime\n}\nstudios(isMain: true) {\n\tnodes {\n\t\tid\n\t\tname\n\t\tsiteUrl\n\t}\n}\nrelations {\n\tedges {\n\t\trelationType(version: 2)\n\t\tnode {\n\t\t\tid\n\t\t\ttitle {\n\t\t\t\tromaji\n\t\t\t\tnative\n\t\t\t\tenglish\n\t\t\t}\n\t\t\tsiteUrl\n\t\t}\n\t}\n}\n\nairingSchedule(\n\tnotYetAired: true\n\tperPage: 2\n) {\n\tnodes {\n\t\tepisode\n\t\tairingAt\n\t}\n}\n\n\t\t}\n\t}\n}","variables":{"season": WINTER,"year": 2019,"page": 1, "perPage": 100}}
""".data(using: .utf8)
print("THE REQUEST \(String(describing: request.httpMethod))")
let task = internalSession.dataTask(with: request, completionHandler: { (data, response, error) in
if let e = error {
print("ERROR: \(e)")
} else if let response = response as? HTTPURLResponse {
print("THE RESPONSE: \(response)")
let json = try! JSONSerialization.jsonObject(with: data!, options: [])
print(json)
}
})
task.resume()
(Please ignore all the forced unwrap optionals and forced try!, this is test code).
Expected Result
I expect the web service to return a JSON similar to this (simplified):
{
"data": {
"Page": {
"pageInfo": {
"hasNextPage": true,
"total": 81
},
"media": [
{
"id": 102882,
"idMal": 37956,
"title": {
"romaji": "3D Kanojo: Real Girl 2",
"native": "3D彼女 リアルガール 2",
"english": "Real Girl 2"
},
"startDate": {
"year": 2019,
"month": 1,
"day": 9
},
"endDate": {
"year": null,
"month": null,
"day": null
},
"status": "RELEASING",
"season": "WINTER",
"format": "TV",
"genres": [
"Romance",
"Slice of Life",
"Comedy"
],
"synonyms": [],
"duration": 23,
"popularity": 3298,
"episodes": 12,
"source": "MANGA",
"countryOfOrigin": "JP",
"hashtag": "#3D彼女リアルガール #3D彼女",
"averageScore": 61,
"siteUrl": "https://anilist.co/anime/102882",
"description": "The second season of <i>3D Kanojo</i>.",
"bannerImage": null,
"coverImage": {
"extraLarge": "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/nx102882-lKp3ExWNzoE6.jpg",
"color": "#e4bb93"
},
"trailer": {
"id": "x6yokzp",
"site": "dailymotion",
"thumbnail": "https://www.dailymotion.com/thumbnail/video/x6yokzp"
},
"externalLinks": [
{
"site": "Official Site",
"url": "http://www.3dkanojo-anime.com/"
},
{
"site": "Twitter",
"url": "https://twitter.com/3Dkanojo_anime"
}
],
"rankings": [
{
"rank": 16,
"type": "RATED",
"season": null,
"allTime": false
},
{
"rank": 19,
"type": "POPULAR",
"season": null,
"allTime": false
},
{
"rank": 16,
"type": "RATED",
"season": "WINTER",
"allTime": false
},
{
"rank": 15,
"type": "POPULAR",
"season": "WINTER",
"allTime": false
}
],
"studios": {
"nodes": [
{
"id": 346,
"name": "Hoods Entertainment",
"siteUrl": "https://anilist.co/studio/346"
}
]
},
"relations": {
"edges": [
{
"relationType": "PREQUEL",
"node": {
"id": 100526,
"title": {
"romaji": "3D Kanojo: Real Girl",
"native": "3D彼女 リアルガール",
"english": "Real Girl"
},
"siteUrl": "https://anilist.co/anime/100526"
}
},
{
"relationType": "SOURCE",
"node": {
"id": 80767,
"title": {
"romaji": "3D Kanojo: Real Girl",
"native": "3D彼女 〈リアルガール〉",
"english": "Real Girl"
},
"siteUrl": "https://anilist.co/manga/80767"
}
}
]
},
"airingSchedule": {
"nodes": [
{
"episode": 7,
"airingAt": 1550595540
},
{
"episode": 8,
"airingAt": 1551200340
}
]
}
}
]
}
}
}
Obtained Result
Instead, I get this:
{
"data": null,
"errors": [
{
"message": "Not Found.",
"hint": "Use POST request to access graphql subdomain.",
"status": 404
}
]
}
The service complains that the request is a GET request and should be POST, and I am explicitly telling URLRequest to use POST instead. If you look at the request in Charles or another proxy, you will indeed see that the request is being done as a GET request, and the httpBody property is being discarded. If you edit the headers and add another header, like so:
let headers = ["content-type": "application/json", "foo": "bar"]
You will see in your proxy that the header is being properly sent.
The only conclusion I can arrive is that there is an internal problem with URLSessionDataTask that forces to do GET requests only. I tried changing it to to a download task, but the same problem is happening. So there must be a problem with my code, but I cannot find it.
EDIT:
Per request, this is the request Postman uses. I have exported the request to CURL to make it easily importable.
curl -X POST \
https://graphql.anilist.co/ \
-H 'Content-Type: application/json' \
-H 'Postman-Token: 49d7e55e-35c2-4a3c-82f0-4eccb1250fc0' \
-H 'cache-control: no-cache' \
-d '{"query":"query (\n\t$season: MediaSeason,\n\t$year: Int,\n\t$format: MediaFormat,\n\t$excludeFormat: MediaFormat,\n\t$status: MediaStatus,\n\t$minEpisodes: Int,\n\t$page: Int,\n){\n\tPage(page: $page) {\n\t\tpageInfo {\n\t\t\thasNextPage\n\t\t\ttotal\n\t\t}\n\t\tmedia(\n\t\t\tseason: $season\n\t\t\tseasonYear: $year\n\t\t\tformat: $format,\n\t\t\tformat_not: $excludeFormat,\n\t\t\tstatus: $status,\n\t\t\tepisodes_greater: $minEpisodes,\n\t\t\tisAdult: false,\n\t\t\ttype: ANIME,\n\t\t\tsort: TITLE_ROMAJI,\n\t\t) {\n\t\t\t\nid\nidMal\ntitle {\n\tromaji\n\tnative\n\tenglish\n}\nstartDate {\n\tyear\n\tmonth\n\tday\n}\nendDate {\n\tyear\n\tmonth\n\tday\n}\nstatus\nseason\nformat\ngenres\nsynonyms\nduration\npopularity\nepisodes\nsource(version: 2)\ncountryOfOrigin\nhashtag\naverageScore\nsiteUrl\ndescription\nbannerImage\ncoverImage {\n\textraLarge\n\tcolor\n}\ntrailer {\n\tid\n\tsite\n\tthumbnail\n}\nexternalLinks {\n\tsite\n\turl\n}\nrankings {\n\trank\n\ttype\n\tseason\n\tallTime\n}\nstudios(isMain: true) {\n\tnodes {\n\t\tid\n\t\tname\n\t\tsiteUrl\n\t}\n}\nrelations {\n\tedges {\n\t\trelationType(version: 2)\n\t\tnode {\n\t\t\tid\n\t\t\ttitle {\n\t\t\t\tromaji\n\t\t\t\tnative\n\t\t\t\tenglish\n\t\t\t}\n\t\t\tsiteUrl\n\t\t}\n\t}\n}\n\nairingSchedule(\n\tnotYetAired: true\n\tperPage: 2\n) {\n\tnodes {\n\t\tepisode\n\t\tairingAt\n\t}\n}\n\n\t\t}\n\t}\n}","variables":{"year":2019,"season":"WINTER","page":1, "limit": 12}}'
Only difference I am seeing here is http:// in your Playground and https:// in your Postman.
So just replace http:// with https:// in your Playground.
I has http request with body:
endpoint = http://127.0.0.1:54400/json
reqBody:
{
"action": "Handler:GET_DICTIONARY",
"locale": "ro",
"data": {"dictionary_type":"MTS"}
}
I need to get stubbed response.
Here my Wiremock mapping:
{
"request": {
"method": "POST",
"bodyPatterns": [
{
"contains": "Handler:GET_DICTIONARY"
}
]
},
"response": {
"headers": {
"Content-Type": "application/json"
},
"status": 200,
"fixedDelayMilliseconds": 3000,
"bodyFileName": "t2a/micb/webclient/_mts_response.json"
}
}
But I has many another requests that content request's body with text:
"Handler:GET_DICTIONARY"
So as result I need to mapping also by
"dictionary_type":"MTS"
because text
and
"dictionary_type":"MTS" AND "Handler:GET_DICTIONARY" create UNIQUE request.
So how I can mapping by request's body use this two matches?
I would suggest to add "matchesJsonPath" in addition to your "contains"
"bodyPatterns": [
{
"matchesJsonPath": "$.data[?(#.dictionary_type == 'MTS')]"
}
]
That will guarantee that all the request with dictionary_type = MTS will mapped to that response.
I am trying to provide a MOCK service that takes a headerName and value from the query and returns it as a (dynamic) header with the response. I am using the following response definition:
"response" : {
"status" : 200,
"statusMessage": "OK",
"headers" : {
"Content-Type" : "application/json",
"{{request.query.debugHeader}}" : "{{request.query.debugHeaderValue}}"
},
"jsonBody" : {
"headerSent": "{{request.query.debugHeader}} {{request.query.debugHeaderValue}}"
},
"transformers": ["response-template"],
"base64Body" : ""
}
The header value is correctly evaluated and put into the response template, however I can't get the header name to be taken from the request.
When sending a request:
http://localhost:8090/example?debugHeader=name&debugHeaderValue=value
The result headers I get back are:
HTTP/1.1 200 OK
Content-Type: application/json
{{request.query.debugHeader}}: value
However I want {{request.query.debugHeader}} to be replaced with the actual request parameter value ("name" in the example above).
Any ideas?
Thanks in advance
Alex
This is supported in WireMock.Net.
The request which you need to specify looks like this:
{
"Guid": "90356dba-b36c-469a-a17e-669cd84f1f05",
"Priority": 0,
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/trans",
"IgnoreCase": false
}
]
},
"Methods": [
"get"
]
},
"Response": {
"StatusCode": 200,
"BodyDestination": "SameAsSource",
"Body": "{\"msg\": \"Hello world : {{request.path}}\" }",
"UseTransformer": true,
"Headers": {
"Content-Type": "application/json",
"Transformed-Postman-Token_{{request.path}}": "token is {{request.headers.Postman-Token}}"
}
}
}
This will add the transformed header Transformed-Postman-Token_{{request.path}} in the response.
Presently this type of variability is not part of the out-of-the-box WireMock application and would have to be custom added. In the WireMock documentation the section Extending WireMock and in particular the part on Transforming Responses.
I found some thread the same problem but their answers not works for me.
I have a post request to check user login.
Url form :
"http://support.xxx.xx:8031/serpapi/login/checkLogin"
Parameters :
["contextInfo" :
["clientId": "1000000", "orgId": "1000001",
"warehouseId": "1000002", "roleId": "0"],
"userName": "hanoiaUser", "password": "hanoiaUser"]
Request :
Alamofire.request(url!, method: .post, parameters: params).responseJSON { (response) in
print(response)
}
Error response :
FAILURE: responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}))
Why am i getting this error? and how to solve it ?
Edit : i'm tried with POSTMAN and it return response :
Content-Type : application/json
Body : raw - json
{
"contextInfo" : {
"clientId":1000000,
"orgId": 1000001,
"warehouseId": 1000002,
"roleId": 0
},
"userName": "hanoiaUser",
"password": "hanoiaUser"
}
RESPONSE :
{
"success": true,
"data": [
{
"userId": 1000003,
"userName": "hanoiaUser",
"token": "b7e804d25065e5c3ac97d765180b7986"
}
],
"error": null
}
Ok, finally I solved the problem
Here's new request :
Alamofire.request(url!, method: .post,encoding : JSONEncoding.default, headers: headers, parameters: params).responseJSON { (response) in
print(response)
}
With Headers :
let headers = [
"Content-Type": "application/json"
]
Set the request header like :
request.allHTTPHeaderFields = ["Content-Type":"application/json"]
I'm having hard time doing something simple. The data I want to send is the following:
{
"nickname":"Rado",
"social": {
"data: {
"accesstoken":"xx",
"applicationId":"xx",
"userId":"xx"
},
"type":"whatever"
}
}
Currently I'm doing that:
let params = [
"nickname": userName,
"social": [
"type": "whatever",
"data": [
"userId": accessToken.userID,
"accesstoken": accessToken.tokenString,
"applicationId": accessToken.appID
]
]
]
Alamofire.request(.POST, "url/users", parameters: params, headers: nil)
.responseJSON { response in
}
As a response I get this:
{
"nickname":"Rado",
"social[data][userId]":"xx",
"social[data][applicationId]":"xx",
"social[data][accesstoken]":"xx",
"social[type]":"something"
}
Any advice will be appreciated!
The solution turned out to be really simple. I was missing encoding: .JSON
Alamofire.request(.POST, "url/users", parameters: params, headers: nil, encoding: .JSON)
.responseJSON { response in
}