I'm trying to perform a GET Request in elm. The function returns a Task that I am trying to perform. Unfortunately, my reference material is Elm 0.17 and what I have gathered is that the signatur for Task.perform has changed.
fetchTasks: MyModel -> String -> Platform.Task Http.Error (Dict String MyTask)
fetchTasks model apiUrl=
{ method = "GET"
, headers = [ Http.header "Content-Type" "application/json"
, Http.header "Authorization" model.token ]
, url = apiUrl
, body = Http.emptyBody
, expect = Http.expectJson (dict taskDecoder)
, timeout = Nothing
, withCredentials = False }
|> Http.request
|> Http.toTask
fetchTaskCmd : MyModel -> String -> Cmd Msg
fetchTaskCmd model apiUrl =
Task.perform AuthError GetTasksSuccess <| fetchTasks model apiUrl
This is my function for the GET Request and the command that performs the tasks. The AuthError and GetTasksSuccess are both Messaged that I have defined. What I have read in the Elm Docs that the new signatur for task perform is
perform : (a -> msg) -> Task Never a -> Cmd msg
What do I have to do to implement to get my command working?
The changes are bigger than you suggest, with the Http library now working primarily with Commands, and not tasks. So the way to write it now is:
makeRequest model apiUrl=
Http.request
{ method = "GET"
, headers = [ Http.header "Content-Type" "application/json"
, Http.header "Authorization" model.token ]
, url = apiUrl
, body = Http.emptyBody
, expect = Http.expectJson (dict taskDecoder)
, timeout = Nothing
, withCredentials = False }
fetchTaskCmd : (Result Error a -> Msg) -> MyModel -> String -> Cmd Msg
fetchTaskCmd msgConstructor model apiUrl =
Http.send msgConstructor (makeRequest model apiUrl)
If you want to use tokens you might also want to consider using my elm-jwt library to help.
Related
I was trying to set custom headers for 'Cache-Control' to achieve a cache at client side(server side has 'Cache-Control: no-cache'). Trying to achieve following two major things.
Response of some of the end points should be cached in memory and
should have an expiration(user defined)
Once expiration time is over, then app should ignore the cache and should fetch data from server.
I followed this link and was able to achieve first target but somehow even after expiry app is still using cache and not triggering any API calls. Not sure if 'max-age' set in header is ignored by the app. Please guide me if I am missing something here.
Here are the code snippets.
Session Configuration:
let sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.requestCachePolicy = .returnCacheDataElseLoad
sessionConfiguration.urlCache = .shared
self.currentURLSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
Request:
if let urlPath = URL(string: <WEB_API_END_POINT>){
var aRequest = URLRequest(url: urlPath, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60)
aRequest.addValue("private", forHTTPHeaderField: "Cache-Control")
let aTask = self.currentURLSession.dataTask(with: aRequest)
aTask.resume()
}
Caching logic:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: #escaping (CachedURLResponse?) -> Void) {
if proposedResponse.response.url?.path.contains("/employees") == true {
let updatedResponse = proposedResponse.response(withExpirationDuration: 60)
completionHandler(updatedResponse)
} else {
completionHandler(proposedResponse)
}
}
CachedURLResponse Extension:
extension CachedURLResponse {
func response(withExpirationDuration duration: Int) -> CachedURLResponse {
var cachedResponse = self
if let httpResponse = cachedResponse.response as? HTTPURLResponse, var headers = httpResponse.allHeaderFields as? [String : String], let url = httpResponse.url{
headers["Cache-Control"] = "max-age=\(duration)"
headers.removeValue(forKey: "Expires")
headers.removeValue(forKey: "s-maxage")
if let newResponse = HTTPURLResponse(url: url, statusCode: httpResponse.statusCode, httpVersion: "HTTP/1.1", headerFields: headers) {
cachedResponse = CachedURLResponse(response: newResponse, data: cachedResponse.data, userInfo: headers, storagePolicy: .allowedInMemoryOnly)
}
}
return cachedResponse
}
}
Was able to fix it at my own. Still sharing the answer in case if helps someone else in need.
Added 'Cache-Control' response header in server response and there we have 'max-age: 60', which indicates that response can be valid till 60 seconds only. So till 60 seconds, app will cache that data and after 60 seconds if making another request, this will fetch fresh data from server.
With that, At client side other than defining your cache policy, nothing else is required.
You can do this either for entire URL Session:
let sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.requestCachePolicy = .useProtocolCachePolicy
sessionConfiguration.urlCache = .shared
self.currentURLSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
Or can do it for specific request.
if let urlPath = URL(string: <WEB_API_END_POINT>) {
var aRequest = URLRequest(url: urlPath, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60)
let aTask = self.currentURLSession.dataTask(with: aRequest)
aTask.resume()
}
I have the following code to connect to a REST API service, authenticate, retrieve a session ID then make further requests passing the session ID to authenticate. The initial request works and I get a HTTP 200 OK plus the session ID in the response, however when I try to make a second request passing the session ID in the header, I get
Caught: groovyx.net.http.HttpResponseException: Bad Request
I know the script can be written much better with the use of classes and try / catch etc. I am still learning both java and groovy so I start by just trying to do everything within the same class.
Any help much appreciated.
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.URIBuilder
import static groovyx.net.http.Method.POST
import static groovyx.net.http.ContentType.*
def url = 'https://1.1.1.1/web_api/'
def uri = new URIBuilder(url)
String CHKPsid
uri.path = 'login'
def http = new HTTPBuilder(uri)
http.ignoreSSLIssues()
http.request(POST,JSON ) { req ->
headers.'Content-Type' = 'application/json'
body = [
"user":"username",
"password":"password"
]
response.success = { resp, json ->
println (json)
CHKPsid = (json.sid)
println "POST Success: ${resp.statusLine}"
}
}
uri.path = 'show-changes'
http.request(POST,JSON ) { req ->
headers.'Content-Type' = 'application/json'
headers.'X-chkp-sid' = '${CHKPsid}'
body = [
"from-date" : "2017-02-01T08:20:50",
"to-date" : "2017-10-21"
]
response.success = { resp, json ->
println (json)
println "POST Success: ${resp.statusLine}"
}
}
String interpolation does not work with single (or triple single quotes). When groovy will evaluate '${CHKPsid}' (single quotes), its value will be ${CHKPsid} (this string). In order to use the value of the variable, you should use double quotes: "${CHKPsid}" or simply just the variable: headers.'X-chkp-sid' = CHKPsid.
So the output of this:
String CHKPsid = "abc123"
println '${CHKPsid}'
println "${CHKPsid}"
will be:
${CHKPsid}
abc123
In order to quickly test what the server receives, you can use httpbin.org or requestb.in
So as well as the correct assignment of the value of the session ID, I found that calling the same HTTPbuilder - http.request the second time even with a change of uri, header and body was the problem. The listening server still saw this as part of the same login API call. My workaround / resolution was to define a 2nd HTTPbuilder with a different name and this now works. I'm interested to know if this is normal behaviour and how others approach this. Thanks.
I have some HTTPBuilder code that behaves differently depending on whether or not I reuse the same HTTPBuilder object to perform two different requests to the same REST service:
def http = new HTTPBuilder( 'https://myBaseURI/' )
http.auth.basic username, password.getPlainText()
http.ignoreSSLIssues()
http.request(GET,JSON) { req ->
uri.path = 'some/api/path/'
headers.'User-Agent' = 'Mozilla/5.0'
} // this request always behaves as expected
http.request(POST, JSON) { req ->
uri.path = 'some/other/api/path'
headers.'User-Agent' = 'Mozilla/5.0'
body = {
// Request body elided for brevity
}
}
The 'correct' behavior is for the POST to return a 201 - Created, but the response comes back as 200 OK unless I create a new HTTPBuilder to handle issuing the second request, in which case, the API call behaves as expected.
Certainly, the cause of the different results could be elsewhere, but I first wanted to make sure I wasn't misusing this object. Are there problems to be aware of when reusing the HTTPBuilder to issue multiple HTTP requests?
Try removing that forward slash at the end when you set uri.path in the GET request.
Looking at the documentation for setPath in URIBuilder you get:
//Set the path component of this URI. The value may be absolute or relative to the current path. e.g.
def uri = new URIBuilder( 'http://localhost/p1/p2?a=1' )
uri.path = '/p3/p2'
assert uri.toString() == 'http://localhost/p3/p2?a=1'
uri.path = 'p2a'
assert uri.toString() == 'http://localhost/p3/p2a?a=1'
uri.path = '../p4'
assert uri.toString() == 'http://localhost/p4?a=1&b=2&c=3#frag'
I understand this to mean that if you set the uri.path of an httpbuilder object with a slash at the end, you have essentially updated the working path so any subsequent relative path updates to uri.path will result in a concatenation of the path. Therefore, your POST in that example ends up pointing at https://myBaseURI/some/api/path/some/other/api/path
The following code doesn't authenticate the user (no authentication failure happens, but the call fails due to lack of permissions):
def remote = new HTTPBuilder("http://example.com")
remote.auth.basic('username', 'password')
remote.request(POST) { req ->
uri.path = "/do-something"
uri.query = ['with': "data"]
response.success = { resp, json ->
json ?: [:]
}
}
But the following works fine:
def remote = new HTTPBuilder("http://example.com")
remote.request(POST) { req ->
uri.path = "/do-something"
uri.query = ['with': "data"]
headers.'Authorization' =
"Basic ${"username:password".bytes.encodeBase64().toString()}"
response.success = { resp, json ->
json ?: [:]
}
}
Why isn't the first one working?
Two things that I can think of off the top of my head.
The .setHeaders method requires a map. Have you tried 'Authorization' : "Basic ${"username:password".bytes.encodeBase64().toString()}" ?
If not, It's a bit more work and code, but you could user the URIBuilder as well. Generally I encapsulate to a different class
protected final runGetRequest(String endpointPassedIn, RESTClient Client){
URIBuilder myEndpoint = new URIBuilder(new URI(Client.uri.toString() + endpointPassedIn))
HttpResponseDecorator unprocessedResponse = Client.get(uri: myEndpoint) as HttpResponseDecorator
def Response = unprocessedResponse.getData() as LazyMap
return Response
}
Hope this helps
Looks like your server isn't fully HTTPBuilder-compilant. It should return 401 code (which is standart behaviour for REST servers, but non-standart for others) for HTTPBuilder to catch this status and resend authentication request. Here is written about it.
Below is the code I am using to post json data to a rest ful service
var client = new JsonServiceClient(BaseUri);
Todo d = new Todo(){Content = "Google",Order =1, Done = false };
var s = JsonSerializer.SerializeToString < Todo>(d);
// client.Post<string>("/Todos/", "[{\"content\":\"YouTube\"}]");
// string payload= "[{\"id\":2,\"content\":\"abcdef\",\"order\":1,\"done\":false}]";
// string payload = #"{""todo"":{ {""content"":""abcdef"",""order"":1}} }";
client.Post<string>("/todos/", s);
I tried passing plain json data , it keep on fialing with message "Bad data". Then i tried serizliing the entity that also didn't work.
You can use PostJsonToUrl, which is included in ServiceStack.Text.