Why is my Julia HTTP.jl handler function throwing an error? - http

I am trying to turn some Julia code into a very basic local microservice, which accepts a POST request with some options supplied via JSON, runs a program, and returns a 200 response. My microservice code is here:
const ROUTERS = Dict()
function __init__()
"""Initialisation function to populate the global variables."""
ROUTERS["ROUTER"] = HTTP.Router()
HTTP.register!(ROUTERS["ROUTER"], "POST", "/run", run_program)
end
function run_program(req)
"""Takes a run request and passes it to the main program, before sending a 200 reponse to say no errors have occured."""
setup = JSON3.read(req.body, Dict{String, Any})
run_program(setup)
return HTTP.Response(200, JSON3.write("Success."))
end
function requestHandler(req)
"""Recieves incoming requests and passes them to the router. Returns the response object."""
local resp
resp = HTTP.handle(ROUTERS["ROUTER"], req)
return resp
end
function run_service(port=8080)
"""Runs the microservice at the specified port."""
HTTP.serve(requestHandler, "0.0.0.0", port)
end
__init__()
This code works with HTTP version 0.9.17, but I updated it to the new version 1.5.5. Now I receive this error whenever I make a request to the running service:
LogLevel(1999): handle_connection handler error
│ exception =
│ UndefVarError: handle not defined
What am I missing here? Have I defined my handler function incorrectly somehow?

There's no HTTP.handle function anymore in HTTP.jl version 1.x, as outlined by the documentation.
You'll probably want something like
"""
Receives incoming requests and passes them to the router. Returns the response object.
"""
function requestHandler(req)
return ROUTERS["ROUTER"](req)
end
instead. Also note that docstrings need to be inserted before the function for the docsystem to pick them up.

Related

Nimlang: Async program does not compile

I'm trying to write a HTTP server that sends a HTTP request and returns the content to client.
Here is the code:
import asynchttpserver, asyncdispatch
import httpClient
let client = newHttpClient()
var server = newAsyncHttpServer()
proc cb(req: Request) {.async.} =
let content = client.getContent("http://google.com")
await req.respond(Http200, content)
waitFor server.serve(Port(8080), cb)
However, I obtain the following compile error message (nim v1.0.0):
Error: type mismatch: got <AsyncHttpServer, Port, proc (req: Request): Future[system.void]{.locks: <unknown>.}>
but expected one of:
proc serve(server: AsyncHttpServer; port: Port;
callback: proc (request: Request): Future[void] {.closure, gcsafe.};
address = ""): owned(Future[void])
first type mismatch at position: 3
required type for callback: proc (request: Request): Future[system.void]{.closure, gcsafe.}
but expression 'cb' is of type: proc (req: Request): Future[system.void]{.locks: <unknown>.}
This expression is not GC-safe. Annotate the proc with {.gcsafe.} to get extended error information.
expression: serve(server, Port(8080), cb)
The serve function expects another expression but do not know how to fix it.
Surprisingly, the code compiles perfectly fine when I remove the HTTP request from the server callback "cb". Does this mean that the serve function expects different callback expressions depending on the callback body ?
OK the problem is that the HttpClient is a global variable and is used in the callback function "cb". As a result the callback function is not GC safe.
So it is enough to instantiate the HttpClient within the callback function:
import asynchttpserver, asyncdispatch
import httpClient
var server = newAsyncHttpServer()
proc cb(req: Request) {.async.} =
let client = newHttpClient()
let content = client.getContent("https://google.com")
await req.respond(Http200, content)
waitFor server.serve(Port(8080), cb)

How to reload code when HTTP server is running?

When starting an http server using HTTP.serve there is apparently no way to reload the code that is actually handling the HTTP request.
In the example below I would like to have the modifications in my_httphandler taken into account without having to restart the server.
For the moment I need to stop the server from the REPL by pressing CTRL+C twice and then run the script again.
Is there a workaround ?
module MyModule
using HTTP
using Mux
using JSON
using Sockets
function my_httphandler(req::HTTP.Request)
return HTTP.Response(200, "Hello world")
end
const MY_ROUTER = HTTP.Router()
HTTP.#register(MY_ROUTER, "GET", "/*", my_httphandler)
HTTP.serve(MY_ROUTER, Sockets.localhost, 8081)
end
I'm not sure whether Mux caches handlers. As long as it does not, this should work:
module MyModule
using HTTP
using Mux
using JSON
using Sockets
function my_httphandler(req::HTTP.Request)
return HTTP.Response(200, "Hello world")
end
const functionref = Any[my_httphandler]
const MY_ROUTER = HTTP.Router()
HTTP.#register(MY_ROUTER, "GET", "/*", functionref[1])
HTTP.serve(MY_ROUTER, Sockets.localhost, 8081)
end
function newhandler(req::HTTP.Request)
return HTTP.Response(200, "Hello world 2")
end
MyModule.functionref[1] = newhandler
Revise.jl lets you automatically update code in a live Julia session. You may be especially interested in entr; see Revise's documentation for details.
When using HTTP.jl: just add #async before HTTP.serve
module MyModule
using HTTP
using Sockets
function my_httphandler(req::HTTP.Request)
return HTTP.Response(200, "Hello world")
end
const MY_ROUTER = HTTP.Router()
HTTP.#register(MY_ROUTER, "GET", "/*", my_httphandler)
#async HTTP.serve(MY_ROUTER, Sockets.localhost, 8081)
end # module
When using Mux.jl: nothing to do, the server is started in the background
using Mux
function sayhellotome(name)
return("hello " * name * "!!!")
end
#app test = (
Mux.defaults,
route("/sayhello/:user", req -> begin
sayhellotome(req[:params][:user])
end),
Mux.notfound())
Mux.serve(test, 8082)
I've added a ticket #587 to HTTP.jl project for developer workflow support. I'm not sure this is your use case or not.
# hello.jl -- an example showing how Revise.jl works with HTTP.jl
# julia> using Revise; includet("hello.jl"); serve();
using HTTP
using Sockets
homepage(req::HTTP.Request) =
HTTP.Response(200, "<html><body>Hello World!</body></html>")
const ROUTER = HTTP.Router()
HTTP.#register(ROUTER, "GET", "/", homepage)
serve() = HTTP.listen(request -> begin
Revise.revise()
Base.invokelatest(HTTP.handle, ROUTER, request)
end, Sockets.localhost, 8080, verbose=true)
Alternatively, you could have a test/serve.jl file, that assumes MyModule with a top-level HTTP.jl router is called ROUTER. You'll need to remove the call to serve in your main module.
#!/usr/bin/env julia
using HTTP
using Sockets
using Revise
using MyModule: ROUTER
HTTP.listen(request -> begin
Revise.revise()
Base.invokelatest(HTTP.handle, ROUTER, request)
end, Sockets.localhost, 8080, verbose=true)
A more robust solution would catch errors; however, I had challenges getting this to work and reported my experience at #541 in Revise.jl.

Unit test Actions on Google Dialogflow locally

I'm trying to unit test a DialogflowApp locally by using the firebase shell environment. (in a cli do firebase experimental:functions:shell and then call my methods)
I have followed this guide by google https://firebase.google.com/docs/functions/local-emulator but they don't use the DialogflowApp where the invoked function tries to bind a request object containing intents and parameters like this ->
exports.myFunction = functions.https.onRequest((request, response) => {
const app = new App({ request, response });
function myMethod(app) {
let myArgument = app.getArgument(MY_ARGUMENT);
app.tell('Here we are responding');
}
let actionMap = new Map();
actionMap.set(MYMETHOD_ACTION, myMethod);
app.handleRequest(actionMap);
});
Regardless of what request object I send in the CLI, like this myFunction(require("../test/testdata.json")), the request body object is empty, like this body: {} which means I can't do app.handleRequest() or app.getArgument(). The error message I get is
RESPONSE RECEIVED FROM FUNCTION: 400, Action Error: no matching intent
handler for: null
I thought that if I populated testdata.json with the json request data shown in Actions on Google -> console.actions.google.com -> Simulator it would be valid data but no.
My question is, how can i mock my request data so that I can start unit testing my fullfillment methods locally?
EDIT 1:
firebase > myMethod.post("/").form(require("../test/testdata.json"))
Sent request to function.
firebase > info: User function triggered, starting execution
info: Function crashed
info: TypeError: Cannot destructure property `parameters` of 'undefined' or 'null'.
if we look in dialogflow_app.js we can see this code for fetching an argument value
getArgument (argName) {
debug('getArgument: argName=%s', argName);
if (!argName) {
error('Invalid argument name');
return null;
}
const { parameters } = this.body_.result;
if (parameters && parameters[argName]) {
return parameters[argName];
}
return this.getArgumentCommon(argName);
}
this.body_ is always just empty {}, regardless of how and what I send into the method when running locally.
EDIT 3
firebase > myMethod({method: "post",json: true, body: require("../test/testdata.json")})
Sent request to function.
firebase > info: User function triggered, starting execution
info: Function crashed
info: TypeError: Cannot destructure property parameters of 'undefined' or 'null'.
Invoking a Firebase HTTPS function using the shell requires a different form. It takes the parameters that the request module does, so in order to emulate a webhook, it will be something like this:
myfunction({
method: 'POST',
json: true,
body: require("../test/testdata.json")
});
These three parameters are important:
You need to specify that this is a POST operation
You need to indicate that the body will be JSON. This will send the correct header and won't try to send the body as x-www-form-urlencoded
You need to include the body. As an object is ok because you've set the json parameter to true.

RSpec: Stub API call that sets global variable

In my Ruby (not Rails) program, I have created global variables in the top-level module. These global variables are set as the clients of external services, so my program makes API calls when they are set. I am trying to figure out how to properly stub these API calls in RSpec.
I would like to test a class inside the top module, that looks more or less like this. Worker does not directly call the global variables anywhere in the class.
module TopModule
class Worker
end
end
Here is the TopModule:
module TopModule
# (As an aside, the external service is AWS)
$client = ExternalService::Client.new(ExternalService.config)
end
I would like to run the RSpec test of TopModule::Worker so it passes:
describe TopModule::Worker do
it 'shows in various ways that Worker functions'
end
However, I get the following error: Real HTTP connections are disabled. Unregistered request: GET http://... with headers {...} (WebMock::NetConnectNotAllowedError)
The stack trace points to the line in TopModule where $client is defined.
I'm also told:
You can stub this request with the following snippet:
stub_request(:get, "http://...").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'...', 'User-Agent'=>'Ruby'}).
to_return(:status => 200, :body => "", :headers => {})
I still have the error when I add the stub to my spec/spec_helper RSpec.configure loop. Here are the relevant parts of the the spec_helper:
require 'webmock/rspec'
require 'codeclimate-test-reporter'
WebMock.disable_net_connect!(allow: 'codeclimate.com')
require 'fileutils'
require 'top_module'
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_doubled_constant_names = true
mocks.verify_partial_doubles = true
end
end
def files_directory
File.dirname(__FILE__) + '/files'
end
Where can I put the stub so it will actually handle the ExternalService API call? I would appreciate your help.
(This code is based on my real code, but not identical)
you can use VCR to stub external api call.
https://github.com/vcr/vcr

Accessing the HTTP GET JSON Object

I am getting my feet wet with Meteor's HTTP methods.
As a test, I am hitting Twitter's api with a method on the server, as follows:
"twitter_user": () ->
Meteor.http.get("https://api.twitter.com/1/users/show.json", {screen_name:"ppedrazzi"})
On the client, I call the method as follows:
twitterUser = Meteor.call("twitter_user")
When trying to access the twitterUser object, it shows as undefined. I was expecting to be able to use twitterUser.data.id or twitterUser.data.name to grab the fields from the resulting JSON, but no luck (since the object does not exist).
Incidentally, if I drop the URL into the browser, I get a JSON object on the page, see example here:
https://api.twitter.com/1/users/show.json?screen_name=ppedrazzi
You should use an async call to your method:
Meteor.call "twitter_user", (error, result) ->
twitterUser = result
Citation from the documentation:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
Note that in this particular case, you can run Meteor.http.get directly on the client:
Meteor.http.get(
"https://api.twitter.com/1/users/show.json",
screen_name:"ppedrazzi",
(error, result) -> twitterUser = result
)

Resources