Introducing snake-server, a simple local web server for Haxe

Many programming languages, or their communities of library authors, offer a way to quickly start a web server in a specific local directory with just one simple command. For instance, Python users can run python3 -m http.server 8000, or Node.js users can install the http-server module globally and run http-server -p 8000. For reference, here’s a big list of http static server one-liners for a variety of languages and their run-times.

When working in Haxe, and compiling to JavaScript in HTML pages, I often need to launch an HTML file in a web browser. It’s always best to use a server instead of double-clicking an HTML file in your operating system’s file explorer because web browsers often behave differently with local files versus content originating from web servers. I typically reach for the Node.js command that I mentioned above. Similarly, the command line build tools used by OpenFL also use the same http-server module with Node.js when you run the lime test html5 command to run your project in a browser.

As a Haxe developer, I can’t help but feel like the Haxe community should be able to reach for a pure Haxe solution that starts a local web server, and it shouldn’t rely on another language’s tooling.

Last week, with that idea in mind, I started working on snake-server 🐍, a Haxe library (built on sys.net.Socket from the Haxe standard library) that can be used to start a web server in a specific directory for local development — all with one quick command.

haxelib run snake-server

It uses port 8000 on local IP 127.0.0.1 with protocol HTTP/1.0 by default, in the current working directory. However, there are command line options for customizing all four of those things.

Install snake-server with a one-time command:

haxelib install snake-server

It’s called snake-server because I actually ported Python’s http.server and socketserver modules to Haxe. To be clear, not as externs, but as pure Haxe that works on any sys target, including the Haxe interpreter, HashLink, hxcpp, and Neko. I figured an official Python module would have the most important edge cases covered, and I could see it was created with a similar raw TCP socket API that’s available in Haxe, so it would be easy enough to port that existing code to Haxe and get a solid solution working as quickly as possible.

The server handles each request in a separate thread. I skipped this part at first because it didn’t seem necessary. However, as I finished implementing HTTP/1.1 support, I realized it was vital because keep-alive requests would block on the final socket read, until timeout, which would block other socket writes from completely flushing, which made pages appear to partially load for several seconds, and then finish all at once after the timeout delay. Each request in a separate thread made everything snappy while supporting the keep-alive optimization.

In your terminal, you can exit the server with the standard Ctrl+C keyboard shortcut. Occasionally, if you exit snake-server, and then try to start snake-server again on the same port too quickly, it’ll say that the port is still in use. It should become available within seconds, though. I think other languages may provide a way to ensure this doesn’t happen, but I’m not sure if that’s catching the Ctrl+C interrupt signal, or if it involves tweaking the socket configuration in some way. I don’t think Haxe exposes what I need, but maybe I’m wrong? Let me know if you know how to fix this issue.

The snake-server source code is on GitHub. Bug fix PRs are welcome, especially for things that behave differently than the original Python implementation. However, please create an issue to ask before submitting new feature PRs. I plan to keep the Haxe code as close to the original Python code as I possibly can, to avoid future maintenance headaches if I port important changes from future Python updates.

I hope that the Haxe community finds snake-server to be useful for their everyday web development needs. If you like snake-server, please consider a monthly donation towards my open source contributions on my GitHub Sponsors page. Thank you!

How to install Apache Flex with Adobe AIR from HARMAN

If you’re still working on projects powered by Adobe AIR (now actively developed by HARMAN) and Apache Flex (formerly Adobe Flex), you’ll eventually need to merge the Flex SDK and AIR SDK together into a single bundle. Back in the day, you could download the Adobe Flex SDK and the latest version of the AIR SDK was already included. Later, after Adobe donated Flex to Apache, they provided instructions to merge or overlay the Flex SDK with the Adobe AIR SDK. Those instructions may still work, if you already have an existing merged SDK, and you simply want to update AIR. However, if you’re starting from scratch (such as if you got a new computer, and you need to download everything fresh), it’s a little more complicated. Luckily, I’ve put together a little Ant script and this guide to help make things easier. Let’s get started.

Things to download

First, if you don’t have it already, you’ll need to download Apache Ant. Ant is used to run the Flex SDK’s installer script that gets a few third-party dependencies and prepares the SDK for use in your favorite IDE. You can download Ant directly from Apache, or (if you prefer) you may be able to get Ant from your favorite package manager, such as Homebrew on macOS or Chocolatey on Windows.

Then, you’ll need to download the two SDKs:

Finally, you’ll need harman-installer.xml, which is a script that I created to enhance the Flex SDK installer script to support HARMAN’s version of Adobe AIR.

Merging the Flex SDK and AIR SDK

Extract the Apache Flex binary distribution .zip or .tar.gz file into a directory. I like to give it a name that includes both SDKs, like apache-flex-4.16.1-air-50.2.4.4.

Then, copy the AIRSDK_Flex_Windows.zip or AIRSDK_Flex_MacOS.zip or AIRSDK_Flex_Linux.zip file into the directory where you extracted the Flex SDK binary distribution. Don’t extract the AIR SDK .zip file. The installer script will do it!

Copy harman-installer.xml into the same directory.

Open a terminal or command prompt, and navigate into the root directory of the Flex SDK that contains harman-installer.xml and the AIR SDK .zip file:

cd apache-flex-4.16.1-air-50.2.4.4

Then, run the following command (be sure to replace 50.2 with the correct AIR SDK major and minor version that you downloaded):

ant -f harman-installer.xml -Dair.sdk.version=50.2

This harman-installer.xml script may take a couple of minutes to complete. If the script ends with a BUILD SUCCESSFUL message, then everything worked correctly, and you’re ready to use your new merged Flex SDK and AIR SDK in your favorite IDE or editor!

At this point, you can also can safely delete the AIR SDK .zip file and harman-installer.xml from your Flex SDK. They’re no longer needed.

Troubleshooting

If the harman-installer.xml script completes with BUILD FAILED instead, it should provide you with some sort of clue to help with troubleshooting. For instance, the following error explains that you probably copied AIRSDK_Flex_MacOS.zip to the wrong location:

BUILD FAILED
/apache-flex-4.16.1-air-50.2.4.4/harman-installer.xml:95: Missing /apache-flex-4.16.1-air-50.2.4.4/AIRSDK_Flex_MacOS.zip

If your terminal or command prompt session can’t find the ant executable, you have a couple of options. 1) You could add the directory containing the ant executable to your operating system’s PATH environment variable (be sure to close your terminal and open a new one). Or, if you prefer, you can use the absolute path to ant, wherever you happened to install it.

/opt/homebrew/bin/ant -f harman-installer.xml -Dair.sdk.version=50.2

OpenFL devlog: HashLink/C compilation

I thought I’d share another update about the new features that I’ve been working on recently for the next versions of OpenFL and Lime. In today’s post, I’m going to talk a bit about expanding OpenFL’s HashLink target to support not just HashLink/JIT, as it has for a while, but also add support for HashLink/C on Windows, macOS, and Linux.

If you’re not familiar, OpenFL is an implementation of the APIs available in Adobe Flash Player (and Adobe AIR) using the Haxe programming language. Projects built with Haxe can be cross-compiled to JavaScript, C++, and many other targets — making it available on the web, desktop apps, mobile apps, game consoles, and more. No browser plugins required, and fully native C++ performance.

HL/JIT vs HL/C

HashLink is a virtual machine for the Haxe programming language. As I understand it, HashLink is considered similar to Adobe AIR in many ways. It has a bytecode format with JIT and garbage collection, which is compiled from a high-level language with a mix of object-oriented and functional programming paradigms. Its APIs are designed to be cross-platform — including window management, graphics, and audio. And it can be extended by loading native code libraries and exposing them to higher-level code.

One interesting thing about HashLink is that it can compile to two different formats. As mentioned above, HashLink supports its own bytecode format, which makes compilation fast and runtime performance relatively decent for day-to-day development. However, it can also cross-compile to low-level C code, which you can compile into fully native machine code with your favorite C compiler on a variety of operating systems (there are HashLink games running on desktop, mobile phones/tablets, and even game consoles). The HL/C workflow may take a bit longer for each individual build, but runtime performance is even better than with HL/JIT. Best of all, your Haxe code will behave exactly the same either way, whether compiled to bytecode or to C.

As of Lime 8.1 and OpenFL 9.3, the HashLink target supports only HL/JIT. As I mentioned, the HL/JIT target performs relatively well and offers a nice development workflow. It’s perfectly fine to release apps using HL/JIT, if it meets your needs, so that made HL/JIT good enough for previous OpenFL contributors to implement as a starting point. After a bit of research, I figured it wouldn’t be too difficult to support HL/C as well, so I started working on it for the upcoming Lime 8.2 release.

Step 1: Haxe compilation for HL/C

From the Haxe side, switching between HL/JIT and HL/C is a minor change. The example below compiles to bytecode for HL/JIT:

haxe -hl bin/MyApp.hl -main MyApp

All you need to do is change the output file extension from .hl to .c when calling the Haxe compiler:

haxe -hl bin/MyApp.c -main MyApp

I added a new lime build hlc command to the Lime tools (or lime build hl -hlc, if you prefer), and that makes Lime generate HL/C code instead of HL/JIT bytecode.

Step 2: C compilation for HL/C

However, an extra step is required after that. Once you have generated some C code, it needs to be passed to a C compiler. Since macOS is my current primary platform, I decided to start there and see how easily I could compile HL/C with the gcc compiler included with Apple’s Xcode.

The Haxe and HashLink documentation both provide pretty good starting points for how to compile the C code. I ended up with something similar to the following command (some file system paths have been simplified a bit, to keep the full command from being overly long):

gcc -O3 -o bin/MyApp -std=c11 -I obj obj/MyApp.c lib/libhl.dylib lib/fmt.hdll lib/mysql.hdll lib/sqlite.hdll lib/ssl.hdll lib/ui.hdll lib/uv.hdll lib/lime.hdll

Basically, you run gcc with a high level of optimization, using the 2011 version of the C language, and including all of the C code and headers generated by Haxe. Additionally, the executable should load libhl.dylib (the core HashLink dynamic library), and a number of .hdll files (other dynamic libraries for HashLink).

On macOS, executables are usually wrapped in .app bundles that macOS treats as a single file, even though it’s more like a directory containing many files. Lime already generated an .app bundle for HashLink/JIT, and that required a bit of finessing to work properly with HashLink/C. The executable generated by gcc was having some trouble finding libhl.dylib and the .hdll files when they were all bundled together inside the .app bundle. I ended up using install_name_tool to force the executable to find the dynamic libraries in the same directory as itself using the @executable_name directory value in the paths. The command looks something like this:

install_name_tool -change lime.hdll @executable_path/lime.hdll bin/MyApp.app/Contents/MacOS/MyApp

Figuring out exactly which install_name_tool sub-command to use (and determining even if that was the correct approach) took a bit of trial and error. To add to the complexity, I was seeing slightly different behavior between the version of HashLink that Lime bundles, and a nightly build of HashLink that I downloaded from the source. Even though Lime bundles HashLink, we also want to support new updates or custom builds, whenever possible. In the end, I got both the bundled and custom versions working, and I was ready to jump to other operating systems.

I thought Linux would be super easy after macOS, since it would also use gcc and both operating systems are Unix-ish. However, I quickly ran into similar issues where the executable couldn’t find the dynamic libraries. After a few hours of banging my head on the desk, I decided to take a break and try Windows instead, and I’d revisit Linux later. Sometimes, when I get stuck, allowing my brain do some background processing for a few hours (or days) can yield surprising successes.

Compiling HL/C on Windows had its own issues, of course. I initially settled on requiring gcc on Windows too, even if it’s not the default C compiler for most Windows developers. I tried to support Visual Studio’s C compiler, but it was failing due to a Haxe compiler issue that I needed to report (more on that in a second).

Anyway, it’s relatively easy to get gcc for Windows thanks to the MinGW-w64 project. I ended up installing it quickly in a terminal with the Chocolatey package manager.

choco install mingw

You could probably also get it with the Scoop package manager, or download the MinGW executables/installer directly from the source.

The gcc command for Windows was very similar. As far as differences go, I mainly needed to set the “windows” subsystem instead of the default “console” subsystem so that an extra terminal didn’t open when double-clicking the .exe file, and I needed to add dbghelp.dll as a dependency, for some reason (if you understand why, I’d be curious for an explanation or workaround). Thankfully, I didn’t need any extra commands similar to install_name_tool on Windows to get it to find the .hdll files.

gcc -O3 -o bin/MyApp -std=c11 -Wl,-subsystem,windows -I obj obj/MyApp.c C:/Windows/System32/dbghelp.dll lib/libhl.dll lib/fmt.hdll lib/mysql.hdll lib/sqlite.hdll lib/ssl.hdll lib/ui.hdll lib/uv.hdll lib/lime.hdll

I was actually able to get a cl.exe (Visual Studio’s C compiler) command working locally, if I did a find/replace for _restrict (which is generated from OpenFL’s TextField.restrict property) on the output C code. It seems that cl.exe treats some things differently than gcc. I submitted a bug report to the Haxe compiler (which has been partially addressed, which I’ll talk about in a second), and I declared MinGW support on Windows good enough to start with.

After I got Windows with MinGW working, I went back to Linux. One morning, I recalled reading about something called rpath, which is used to find dynamic libraries on both macOS and Linux. However, since I hadn’t used it on macOS, I didn’t think much of it when I saw it mentioned with Linux too. This time around, I decided to use rpath as my starting point for web searches, and eventually, I found some random example that mentioned an $ORIGIN value that could be passed to the linker options that worked similarly to @executable_name on macOS.

I also had to specify the libraries a bit differently using -L for the parent directory and listing the .so and .hdll files by name instead of full paths.

gcc -O3 -o bin/MyApp -std=c11 -Wl,-rpath,$ORIGIN -I obj obj/MyApp.c -L lib libhl.dylib fmt.hdll mysql.hdll sqlite.hdll ssl.hdll ui.hdll uv.hdll lime.hdll

I tried doing the same thing with rpath and -L on macOS, to make the commands more consistent, but apparently, gcc (or something else in the operating system) works differently with dynamic libraries, so the macOS command(s) had to remain different from Linux. Not a big deal, but I just wish they were more similar.

At this point, I had a few extra moments to spare, and I realized that I could probably allow HL/C code to be compiled with clang instead of gcc too. It turns out that I was right, and the same command line options worked without changes. Only the command name changed from gcc to clang. At least on macOS and Linux. I ran into some strange issues with certain Windows APIs not being recognized, for some reason, and I honestly don’t know enough about clang (or C development in general) to know how to fix that. Perhaps someone else can contribute that, and we’ll have a full range of compilers supported on all platforms.

There was one other detail that came up. On Apple Silicon CPUs for macOS, gcc and clang default to compiling for Apple Silicon, but currently, Lime’s and HashLink’s .hdll libraries are strictly for Intel CPUs. So I needed to precede the gcc command with arch command to ensure that it compiles for Intel to be compatible with the libraries.

arch -x86_64 gcc [options]

Lime and HashLink for Intel both work well on Apple Silicon with Rosetta, by the way. However, I don’t doubt that we’ll revisit full Apple Silicon support in the future.

After I submitted the bug report about certain HL/C code failing to compile with Visual Studio, the Haxe compiler team fixed the issue right away (at least, partially) and released Haxe 4.3.3 soon after. Most vanilla OpenFL apps should compile with this specific Haxe version, but there are some edge cases affecting Feathers UI that will require another small update to Haxe (or some renamed variables in Feathers UI).

With the other C compilers working on all platforms, I went back to trying to get cl.exe from Visual Studio to compile. I decided that the first step would be to get the cl.exe command working in Visual Studio’s special developer console, which automatically sets up various environment variables for you. I knew I’d eventually want to be able to build OpenFL apps from any random Windows terminal window, but I could look into that later. One step at a time.

I ended up with a command that looks something like this:

cl.exe /Ox /Fe:bin/MyApp.exe -I obj obj/MyApp.c -L lib libhl.lib fmt.lib mysql.lib sqlite.lib ssl.lib ui.lib uv.lib lime.lib /link /subsystem:windows

When an executable is built on Windows with Visual Studio, and it uses a .dll file (or .hdll, in the case of HashLink libraries), the .dll file is associated with a seperate .lib file. gcc and clang seem to be able to link directly to a HashLink .hdll file, but Visual Studio’s cl.exe wants the .lib file instead. HashLink is distributed with .hdll and .lib files for its core library. Lime wasn’t bundling those .lib files in 8.1 and older, but I made a minor tweak to the Lime build process to copy those next to the .hdll files that we were already bundling (the .lib files were already built, but they were simply not copied because HL/JIT doesn’t need them).

With that working, the next step was to figure out how to set up Visual Studio’s build environment in a random command prompt instead of requiring OpenFL users to open the Visual Studio developer console.

First, I had to figure out where Visual Studio was installed. I learned that Visual Studio comes with an executable called vswhere.exe, that is guaranteed to be available at the following location if Visual Studio is installed, regardless of which version of Visual Studio we’re dealing with:

C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe

Running vswhere.exe with the following options should print the location of Visual Studio:

vswhere.exe -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath

Visual Studio includes a batch file, called vcvarsall.bat, which can be used to set the appropriate environment variables. Using the installation path returned by vswhere.exe, you just need to append VC\Auxiliary\Build\vcvarsall.bat to the end. It takes a few options, but the only required one is the archtecture. Lime uses the x86_64 architecture (sometimes abbreviated to x64) by default on Windows, so I can pass x64 for the architecture option.

vcvarsall.bat x64

Finally, I needed to combine everything together into a single command that Lime’s tools could run. This one was a little trickier than I expected, and it took me some trial and error, but I ended up launching a separate cmd.exe instance to make things work. The command looks something like this (cl.exe options omitted for brevity):

cmd.exe /s /c vcvarsall.bat x64 && cl.exe [options]

Whew! And that was the last C compiler that I wanted to get working! Now, users won’t need to manually run all of the commands above. They’ll just need to run this one, and Lime’s tooling will do all of the heavy lifting:

lime build hlc

Much better!

Where can I find the code?

If you want to try out the new hlc target for OpenFL, you’ll need to check out Lime’s 8.2.0-Dev branch on Github, or you can download both the lime-haxelib artifact from a successful Github Actions Lime 8.2.0-Dev nightly build. If you check out Lime’s source code from Github, you’ll need to manually rebuild the .hdll binary files and Lime’s command line tools, so you may find it easier to go with the pre-built artifacts. Of course, nightly builds are not necessarily ready for release to Haxelib, so use at your own risk in production. You may encounter some bugs, but we’d love any feedback you can give. Thanks, and happy coding!

If you love these devlogs, and my contributions to OpenFL and Lime in general, please consider a monthly donation as a token of appreciation on my GitHub Sponsors page. Thank you!