Jim Lynch Codes
  • Blog
  • About Jim

Writings about one coder's stories & experiences.

SOLVED - "XMLHttpRequest is not defined" with ClojureScript Core.Async

1/10/2020

0 Comments

 
I solved a ClojureScript error today, and after getting stuck with a strange error and scouring the internet for information I ended up on this Github issue thread where I had been asking for help on this exact issue almost 2 years ago! Well then, that seems to me like a great candidate for a blog post! So, in this post I'll show you how to get past the XMLHttpRequest Error that you might encounter when building a ClojureScript Lambda function that makes an http request! By the way, you can check out the full source code for this project here. Alright, Let's go!

Scaffold Out A Serverless ClojureScript Project

I'm a huge fan of the serverless framework, and I really love their cli tool. They have loads of templates which you can use to scaffold serverless functions, but there is currently only one ClojureScript template, and it is currently named "aws-clojurescript-gradle". Notice how the JVM Clojure and ClojureScript templates are the very first ones in the list. This has nothing to do with spelling and is of course because Clojure is the greatest, most beautiful language of all! ? 
Picture
​To scaffold out a new project, go into a fresh directory where you'd like your project to live and run this command:
sls create --template aws-clojurescript-gradle
And now you've got a clean, barebones lambda function ready to go! Now push an "initial scaffold" commit up to git and call yourself a ClojureScript developer!

Say Hello To The Files

Open up the src/main/clojurescript/serverless/functions.cljs file, and you should see two functions here: "hello" and "now". If you look in the serverless.yml file you should see a section for "functions" (meaning  your serverless functions). Each has a "handler" which refers to the actual ClojureScript function which is the entry point of execution when the serevrless function is called. Also under each function is a section for the "event http path", and the value here sets up the serverless function  to be available as a REST endpoint at "localhost:3000/value". In the scaffolded project the names "hello" and "now" are the same for the . serverless function name, ClojureScript entry point function name, and endpoint path. Many engineers find that this consistency keeps this straightforward and easy to stay organized, however you may choose to follow a different naming scheme different if you so desire! Another file worth noting is build.gradle which is where you specify the needed dependencies, among other things.

​Write Some Clojure Code!

Okay, now that we have an understanding of the key files in our project, let's write some code! Let's take a look at our functions.cljs file first. Notice at the bottom of the file there is a little block of code that is basically setting the "module.exports" very similarly to how it would be done in regular old node.js! Just keep things simple, I'm going to use "example" as the name for serverless function, ClojureScript function, and the endpoint path. First, let's update the exports map to include this new example function.
(set! (.-exports js/module) #js
    {:hello hello
     :now now
     :example example})
Great, now let's create a little function named "example" that calls out to some endpoint. 
(defn example [event ctx cb]
  (go (let [response (<! (http/get "https://sv443.net/jokeapi/category/Programming"))
            theGloriousData  (->> response
                                  (:body)
                                  (clj->js)
                                  (.stringify js/JSON))]
        (cb nil (clj->js
                 {:statusCode 200
                  :headers    {"Content-Type" "application/json"}
                  :body       theGloriousData})))))
We'll need to import the functions and macros from core.async and cljs-http in order to make our http requests.  These are the two most popular libraries for doing network calls in ClojureScript, and for good reason- the incredibly clean and succinct syntax allows you to write code that is just so beautiful that it brings tears of joy to my eyes. We'll also need to explicitly import the functions and macros from external libraries so that they can be used in this file. In the end my "serverless.functions" namespace block looks like this:
(ns serverless.functions
  (:require-macros [cljs.core.async.macros :refer [go]])
  (:require [cljs.nodejs :as nodejs]
            [cljs-http.client :as http]
            [cljs.core.async :refer [put! chan <!]]))

Add Example To The Serverless Config Yaml

Okay, now that the code is there we need to edit out serverless yaml file. Under the functions sections you should see definitions for the serverless functions "hello" and "now". Let's create another one called "example" that points to the "example" function we created and will be accessible at the "/example" route. 
example:
    handler: build/clojurescript/main/functions.example
    events:
      - http:
          path: example
          method: get
While you're in this file, feel free to add the "serverless-offline" plugin at the bottom of the file:
plugins:
  - serverless-offline

Add ClojureScript Dependencies In "build.gradle" 

In the dependencies section let's add the ClojureScript libraries we want to use, which are core.async and cljs-http. In general, you can find the installation commands and versions on the libarry Github page or on the  Clojars website.
dependencies {
  implementation 'org.clojure:clojurescript:1.10.312'
  implementation 'org.clojure:core.async:0.6.532'
  devImplementation 'org.clojure:java.classpath'
  compile 'cljs-http:cljs-http:0.1.46'
}
Note that if you try to build (./gradlew run builkd) and run (sls offline start) your app then hitting the endpoint will result in the dreaded XMLHttpRequest Error:
Picture
​Reference Error ----------------------------------------
 
  ReferenceError: XMLHttpRequest is not defined
      at goog.net.DefaultXmlHttpFactory.createInstance (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:2488:126)
      at Object.goog.net.XmlHttp (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:2486:1501)
      at goog.net.XhrIo.createXhr (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:2500:250)
      at goog.net.XhrIo.send (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:2495:351)
      at cljs_http.core.xhr (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3610:28)
      at cljs_http.core.request (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3621:437)
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3640:13
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3659:344
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3664:54
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3633:223
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3634:397
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3648:250
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3651:4
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3653:239
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3654:402
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3643:29
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3656:100
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3669:342
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3672:62
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3664:254
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3666:466
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3672:332
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3636:458
      at Function.cljs_http.client.get.cljs$core$IFn$_invoke$arity$variadic (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3678:141)
      at cljs_http.client.get (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3677:381)
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3701:45
      at d (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3699:347)
      at b (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3700:350)
      at cljs.core.async.impl.ioc_helpers.run_state_machine (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3268:131)
      at cljs.core.async.impl.ioc_helpers.run_state_machine_wrapped (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3268:278)
      at /Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3702:400
      at Immediate.cljs.core.async.impl.dispatch.process_messages (/Users/jim/Git-Projects/XMLHttpRequest-Cljs-Core.Async-Error-Solved/build/clojurescript/main/functions.js:3225:232)
      at runCallback (timers.js:705:18)
      at tryOnImmediate (timers.js:676:5)
      at processImmediate (timers.js:658:5)
      at process.topLevelDomainCallback (domain.js:126:23)
 
     For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.
 
  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com
 
  Your Environment Information ---------------------------
     Operating System:          darwin
     Node Version:              10.17.0
     Framework Version:         1.59.3
     Plugin Version:            3.2.5
     SDK Version:               2.2.1
     Components Core Version:   1.1.2
     Components CLI Version:    1.4.0

Add Xhr2

The solution, friends, is to add the node library XMLHttpRequest javascript npm package xhr2. In general, you can check the versions tab there on the npm page or even the releases page of the github library. For this npm package in particular both places show the newest version to be 0.2.0 so that's what I entered in the gradle.build file! XMLHttpRequest apparently is packaged only with browsers and not node, and for whatever reason declaring the cljs-http ClojureScript library doesn't automatically add xhr2 as well. The npm package is called xhr2, and in order to add it to your project all you need to do is add a line for the version you'd like to use in the "npmDeps" section of the build.gradle file:
 npmDeps = ['dayjs': '1.8.17',
             'xhr2': '0.2.0']

Require Xhr2 In Your Code

Now that we have installed the xhr2 library, we need to explicitly make it available in our code by adding this line directly under the first ns block in your functions.cljs ​file: 
(set! js/XMLHttpRequest (nodejs/require "xhr2"))
Notice how this is just above Clojure code is expressing an extremely idea as this node.js javascript code:
const XMLHttpRequest = require("xhr2")
But only one is these lines is truly an expression... ?

Try It Out

Now, your lambda function should build successfully when you run the build command:
./gradlew clean build
Then you can host it locally with the serverless-offline plugin:
severless offline start
You should then be able to send a GET request to http://localhost:3000/example which should then return you the payload of whatever endpoint you entered in the http-cljs call. Horray! Here's a screenshot to see it in Postman:
Picture

And Now You're In Business!

So now your internet-connected ClojureScript function should compile and work great! Of course you'll want to lock down your endpoints more with CORS, API keys, Auth token, etc., but this is a functioning backend microservice you can deploy right now to run on NodeJS AWS Lambda- written in Clojure! Don't let this little XMLHttpRequest issue trip you up, and enjoy building out super scalable and efficient serverless functions in the most beautiful programming language out there. ??

Happy coding!
0 Comments

Your comment will be posted after it is approved.


Leave a Reply.

    ​Author

    Picture
    The posts on this site are written and maintained by Jim Lynch. About Jim...
    Follow @JimLynchCodes
    Follow @JimLynchCodes

    Categories

    All
    Actionscript 3
    Angular
    AngularJS
    Automated Testing
    AWS Lambda
    Behavior Driven Development
    Blockchain
    Blogging
    Business Building
    C#
    C / C++
    ClojureScript / Clojure
    Coding
    Community Service
    CS Philosophy
    Css / Scss
    Dev Ops
    Firebase
    Fitness
    Flash
    Front End
    Functional Programming
    Git
    Go Lang
    Haskell
    Illustrations
    Investing
    Java
    Javascript
    Lean
    Life
    Linux
    Logic Pro
    Music
    Node.js
    Planning
    Productivity
    Professionalism
    Python
    React
    Redux / Ngrx
    Refactoring
    Reusable Components
    Rust
    Security
    Serverless
    Shell Scripting
    Swift
    Test Driven Development
    Things
    TypeScript
    Useful Sites
    Useful Tools
    Video
    Website Development
    WebStorm
    Writing

    Archives

    August 2021
    February 2021
    January 2021
    October 2020
    September 2020
    May 2020
    April 2020
    February 2020
    January 2020
    December 2019
    October 2019
    September 2019
    August 2019
    July 2019
    June 2019
    May 2019
    April 2019
    March 2019
    February 2019
    January 2019
    December 2018
    November 2018
    October 2018
    September 2018
    August 2018
    June 2018
    May 2018
    April 2018
    March 2018
    February 2018
    January 2018
    December 2017
    November 2017
    October 2017
    September 2017
    August 2017
    July 2017
    May 2017
    April 2017
    March 2017
    February 2017
    January 2017
    December 2016
    November 2016
    October 2016
    September 2016
    August 2016
    July 2016
    June 2016
    May 2016
    April 2016
    March 2016
    February 2016
    January 2016
    December 2015
    November 2015
    October 2015

    RSS Feed

  • Blog
  • About Jim
JimLynchCodes © 2021