http://www.alienfactory.co.uk/feeds/all
Alien-Factory
The personal website and technical time capsule of SlimerDude, also known as Steve Eynon
2020-05-21T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
/favicons/favicon-96x96.png
© 2013-2024 Steve Eynon
afAtom
http://www.alienfactory.co.uk/articles/intro-to-f4
An Introduction to the F4 IDE
2014-08-28T00:00:00+02:00
2020-05-21T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p><div class="noPrint"><iframe width="800" height="450" src="https://www.youtube.com/embed/YWtVvXDkdO8?showinfo=0" frameborder="0" allowfullscreen style="border: 4px solid #20ee23;"></iframe></div><div class="text-xs-center" style="width: 800px"><a href="https://www.youtube.com/watch?v=YWtVvXDkdO8" class="lead">Watch Video on YouTube </a></div></p>
<p class="lead">Writing code in text editors can quickly get tiresome, so here we look at an IDE that offers continuous compilation, contextual auto-complete, and a debugger!</p>
<p>The F4 IDE, written by <a href="http://www.xored.com/">Xored</a> and maintained by <a href="https://www.fantomfactory.com/">Fantom Factory</a>, is a great tool for developing Fantom code but may look a little scary to the uninitiated.</p>
<p>This article guides you through the process of creating your first Fantom project, and then running and debugging it, all the while explaining essential F4 and Fantom concepts along the way.</p>
<p>F4 is an <a href="https://www.eclipse.org/home/index.php">eclipse workbench</a> created from the 4.9 (2018-09) release. So if you need to google for answers, note the answer may be related to eclipse and not specifically F4.</p>
<p>Note the source code is freely available on <a href="https://github.com/xored/f4">GitHub</a>. This is also a good place to report any issues and bugs found.</p>
<ul class="blendChildren">
<li><a href="#install">Install</a></li>
<li><a href="#firstRun">First Run</a></li>
<li><a href="#firstGlimpse">First Glimpse</a></li>
<li><a href="#firstProject">First Project</a></li>
<li><a href="#addingAnInterpreter">Adding an External Interpreter</a></li>
<li><a href="#wheresMyPod">Where's My Pod?</a></li>
<li><a href="#runningFantomScripts">Running Fantom Scripts</a></li>
<li><a href="#debuggingFantomScripts">Debugging Fantom Scripts</a></li>
<li><a href="#handyShortcuts">Handy Shortcuts</a></li>
</ul>
<h2 id="install">Install</h2>
<p>F4 requires Java 8 or later - so make sure you have a relevant Java JRE installed.</p>
<p>Then download the relevant version of F4 .zip file from the <a href="https://github.com/xored/f4/releases">"Releases" tab in GitHub</a> (~158MB):</p>
<p class="lead"><a href="https://github.com/xored/f4/releases">F4 Releases on GitHub</a></p>
<p>Unzip it in the usual place for your apps / OS. All examples in this article will be using Windows: <code>C:\Apps\</code></p>
<p>If you have a choice of 32bit or 64bit versions of F4, make sure it matches the 32bit or 64bit version of your installed Java.</p>
<p>Make sure that if you are using the 32bit or 64bit version of Java, that you are using ther same 32/64bit version of your Java JRE.</p>
<p>If you have multiple versions of Java installed, or if you get an error when starting F4, make sure F4 is picking up the correct JRE. Do this by editing <code>F4.ini</code> (a text file inside your F4 folder) and specifying a <code>-vm</code> option that points to where the <code>java.exe</code> lives. For instance, I add the following just before the <code>-showsplash</code> line:</p>
<pre class="syntax microlight notForIe ">-vm
C:/Apps/Java/openjdk-1.8.0_101/bin</pre></pre>
<p>Adjust yours to point to the correct java file.</p>
<p>See <a href="https://wiki.eclipse.org/Eclipse.ini#Specifying_the_JVM">eclipse.ini - Specifying the JVM</a> for more details.</p>
<p>F4 comes bundled with its own embedded Fantom 1.0.74 runtime, so does not require Fantom to be installed on your system. That said, it is useful to have your own Fantom installation because it is easier to customise and you can re-use it outside of F4, like on the command line.</p>
<p>So go ahead and also <a href="/articles/how-to-install-fantom">install Fantom</a>, I'll explain how to tie it into F4 later. We will also need it later to copy over some example source files.</p>
<h2 id="firstRun">First Run</h2>
<p>In Windows you launch F4 by running <code>F4.exe</code></p>
<p>The first time F4 is run it will ask you for a Workspace location.</p>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.workspaceLauncher.png" alt="F4 Workspace Launcher"></p>
<p>The workspace is used to store settings, preferences and dialogue histories. They are all kept nicely hidden inside a <code>.metadata/</code> directory. The workspace is also a default area that F4 looks for and creates projects, though projects don't have to be kept there.</p>
<h2 id="firstGlimpse">First Glimpse</h2>
<p>Okay, here's my 30 second overview of an eclipse workbench:</p>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.workbench.png" alt="F4 Workspace Launcher"></p>
<p>Files are edited in the <code>Editor</code> area. All other sections are called <code>Views</code>.</p>
<p>Views may be freely dragged and docked to all areas of the workbench. You can even close Views. To re-open a closed view, go to <code>Window -> Show View -> Other</code>. Important Views are:</p>
<ul class="blendChildren">
<li><strong>Fantom Explorer</strong> - A tree view of your projects.</li>
<li><strong>Problems</strong> - Shows compilation errors.</li>
<li><strong>Console</strong> - Shows the output of programs you run.</li>
<li><strong>Script Unit Test</strong> - Shows unit test results.</li>
<li><strong>Tasks</strong> - Shows all <em>TODOs</em> and <em>FIXMEs</em></li>
</ul>
<p>All those views are pretty generic and standard for eclipse.</p>
<p>The arrangement of open views and their docked location is called a <code>Perspective</code>. The perspective changes depending on what you're doing. For example, there's a dedicated perspective for debugging and a dedicated perspective for syncing code (via SVN, Hg, git, etc...). Most of your time is spent in the <code>Fantom</code> perspective.</p>
<p>For more detailed information on how to use the eclipse workbench, see the official <a href="http://help.eclipse.org/neon/topic/org.eclipse.platform.doc.user/gettingStarted/qs-02a.htm">Eclipse Workbench Basic Tutorial</a>.</p>
<h2 id="firstProject">First Project</h2>
<p>F4 does not really deal with individual Fantom files, but rather Fantom projects. More specifically it builds Fantom pods. One F4 project builds one Fantom pod.</p>
<p>Pods are to Fantom, as Jars are to Java, and Gems are to Ruby. If you don't know what pods are, or how to build them, then read:</p>
<ul class="blendChildren">
<li><a href="/articles/inside-a-fantom-pod">Inside a Fantom Pod</a>
<p>Inspects the contents of the BedSheet library (pod) to see what it contains.</p>
</li>
<li><a href="/articles/inside-a-fantom-build-script">Inside a Typical Fantom Build Script</a>
<p>Explains how a typical Fantom build script works by inspecting the one used by BedSheet.</p>
</li>
</ul>
<p>We're going to create an F4 project that builds a pod called <code>examples.pod</code>. It will contain the Fantom <a href="http://fantom.org/doc/examples/index#sys">sys</a> and <a href="http://fantom.org/doc/examples/index#fwt">fwt</a> examples.</p>
<p>We are not going to compile <em>all</em> the examples into a pod because some of the examples share the same class and / or file name. In a Fantom pod all the <code>.fan</code> files must have different file names, and all the class names must also be different. (As explained in <a href="/articles/inside-a-fantom-pod">Inside a Fantom Pod</a>.)</p>
<p>An F4 project is defined by 3 configuration files in the project's root directory along side the <code>build.fan</code> file. They are:</p>
<ul class="blendChildren">
<li><code>.buildpath</code> (optional)</li>
<li><code>.classpath</code> (optional)</li>
<li><code>.project</code></li>
</ul>
<p>Yes, all the filenames start with a dot! This is so F4 hides them (by default) from the Fantom Explorer View.</p>
<p><code>File -> New -> Fantom Project</code> will start the New Project Wizard whose main output is creating those files, and a basic <code>build.fan</code>. Enter a project name of <code>Examples</code> and click <code>Finish</code>.</p>
<p class="warn">Hooray, Your project has now been created!</p>
<p>Ah, but you may have noticed the one warning in the Problems View stating that the project has no source code:</p>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.problem.noSrcCode.png" alt="Look Ma! No Source Code!"></p>
<p>Lets correct this. Assuming you've installed Fantom, copy the following Fantom directories to the Example project's <code>fan/</code> directory. (By convention all Fantom code is put inside a top level <code>fan/</code> directory.)</p>
<pre class="syntax microlight notForIe ">%FAN_HOME%/examples/fwt
%FAN_HOME%/examples/sys</pre></pre>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.workflow.copyFiles.png" alt="Son! Put your Source Code back right now!"></p>
<p>Once copied, F4 does not pick up the new files straight away as it maintains a cache of the file system. To force F4 to update, right click on the project in the Fantom Explorer View and select <code>Refresh (F5)</code>.</p>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.fantomExplorer.noDepends.png" alt="Examples Project with Errors"></p>
<p class="danger">Argh!!! Errors!!!</p>
<p>Not to worry. The Problems View tells us:</p>
<pre class="syntax microlight notForIe ">Using 'fwt' which is not a declared dependency for 'Examples'</pre></pre>
<p>To add a dependency, open the <code>build.fan</code> and edit the depends variable to add <code>gfx</code>, <code>fwt</code>, and <code>concurrent</code>:</p>
<pre class="syntax microlight notForIe fantom">depends = [
"sys 1.0",
"gfx 1.0",
"fwt 1.0",
"concurrent 1.0"
]
</pre></pre>
<p>To understand the version syntax we've just added, see the <a href="http://fantom.org/doc/sys/Depend.html">Depends</a> class. Note that <code>sys 1.0</code> actually means <code>sys 1.0.*</code> so it happily matches the current Fantom version - 1.0.74.</p>
<p>Note that our pod is called <code>examples</code> - it's a Fantom convention that pod names start with a lowercase letter.</p>
<p>You may have noticed that F4, through some regular expression parsing magic, also altered and updated the <code>srcDirs</code> expression to add <code>fan/sys/</code> and <code>fan/fwt/</code>. You can add, edit and delete whatever you like in <code>build.fan</code>, but leave the <code>srcDirs</code> line alone. Well, you can edit it if you like, but F4 will just change it back!</p>
<p>When you save a source file, F4 will re-compile the entire project and attempt to make a <code>.pod</code> file. So those errors won't go away until you hit <code>save</code>.</p>
<p class="warn">Note you can not run any code until <em>ALL</em> errors are fixed.</p>
<p>In F4, when you run a class as a script, it is run from the <code>.pod</code> file. If you have errors / red crosses in your project (check the Problems View) then the <code>.pod</code> can not be built, and you can not run any new code.</p>
<h2 id="addingAnInterpreter">Adding an External Interpreter</h2>
<p>F4 uses it's own compiler to compile Fantom code, but it compiles against the libraries found in the project's Interpreter. And when you run / launch classes, they too are run with libraries in the project's Interpreter.</p>
<p>F4 supplies it's own embedded Fantom 1.0.74 Interpreter by default. But this may be changed to use any Fantom installation. This is handy for installing custom pods and re-using Fantom environments.</p>
<p>First, unzip the Fantom version you wish to use. This example will use Fantom 1.0.74 unzipped to <code>C:\Apps\fantom-1.0.74\</code></p>
<p>Then in F4 go to <code>Window -> Preferences -> Fantom -> Interpreters</code>. Then click <code>Add...</code> to bring up the Add Interpreter dialogue.</p>
<p>Click <code>Browse...</code> on the Interpreter Executable line and navigate to where the <code>fan.bat</code> is located in the Fantom installation. This should fill out all the fields as follows:</p>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.addInterpreter.png" alt="Add Interpreter Dialogue"></p>
<p>Click <code>OK</code> and it will be added to the list of Interpreters. Make sure that the newly added Fantom 1.0.74 (the non embedded one) is checked - this makes it the default interpreter for all projects.</p>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.interpreters.png" alt="Add Interpreter Preferences"></p>
<p>Click <code>OK</code>. If your project is not rebuilt goto <code>Project -> Clean... -> Clean all projects</code> to force a rebuild.</p>
<h2 id="wheresMyPod">Where's My Pod?</h2>
<p>If the Problems View is clear, the <code>examples.pod</code> should be built. But where is it? By default, output directories are hidden in the Fantom Explorer View, but if you use a normal file explorer you should find it here:</p>
<pre class="syntax microlight notForIe "><project-dir>/bin/examples.pod</pre></pre>
<p>Right click on the project, select <code>Properties -> Fantom -> Compiler</code> and note the <code>Pod output dir</code>:</p>
<pre class="syntax microlight notForIe ">bin/</pre></pre>
<p>To test the rebuild, delete the <code>.pod</code> file, or the <code>bin/</code> directory. Then clean the project, <code>Project -> clean...</code> and you should see the re-generated <code>examples.pod</code> file.</p>
<p>If the pod does not get re-built, then you have an error in your F4 project.</p>
<h2 id="runningFantomScripts">Running Fantom Scripts</h2>
<p>Given our project is building, we should be able to run any class as a Fantom script.</p>
<p>Right click on any of the example <code>.fan</code> files, in either the Fantom Explorer View or the Editor, and select <code>Run As... -> Fantom Class</code>. Output from the program should appear in the Console View.</p>
<p>When you run a class in F4, it creates a <code>Run Configuration</code>. To see and edit run configurations, click the <code>Run As</code> button in the toolbar, or right click a fantom file and go to <code>Run As -> Run Configurations...</code>.</p>
<p>To quickly access and run a recent configuration, click the down chevron next to the <code>Run As</code> button on the toolbar. (It looks like a white arrow in a green circle.)</p>
<h2 id="debuggingFantomScripts">Debugging Fantom Scripts</h2>
<p>Let's try debugging a Fantom script. We'll debug <code>lists.fan</code> in the <code>sys</code> directory.</p>
<p>Open up <code>lists.fan</code>, find the <code>search()</code> method. We want to inspect the value of the <code>x</code>, <code>y</code>, <code>z</code> variables.</p>
<p>Double click in the left hand margin on one of the <code>show</code> methods and a blue ball should appear. Well done, you've just created a breakpoint. Now right click the editor and select <code>Debug As -> Fantom Class</code>.</p>
<p>A dialogue should appear asking if you want to switch the Debug perspective. Click <code>Switch</code>.</p>
<p>The program should run, printing stuff out to the Console View but stop when it reaches the breakpoint:</p>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.debugging.noVars.png" alt="Debugging Perspective"></p>
<p>Note how our <code>x</code>, <code>y</code>, <code>z</code> variables <em>DO NOT</em> appear in the Variables View!</p>
<p>That's because, by default, Fantom does not generate any debug info. Let's change that.</p>
<p>Click the red square in either the Debug or Console View to terminate the program. Then close down F4.</p>
<p>Go to where the external interpreter is installed and open up <code>%FAN_HOME%/etc/sys/config.props</code>. Find the line with <code>// debug=true</code> and uncomment the line be removing the <code>// </code> so the line is now just <code>debug=true</code>:</p>
<pre class="syntax microlight notForIe ">// If debug is set to true, then the runtime emits debug
// information such as the LocalVariableTable for debuggers
// This property can only be configured by bootstrap homeDir.
debug=true
</pre></pre>
<p>Save config.props, restart F4 and clean the project to force the <code>.pod</code> to be rebuilt; this time with debug info! ( <code>Project -> Clean...</code> ) The breakpoint in <code>lists.fan</code> should still be in the same place so <code>Debug As -> Fantom Class</code>.</p>
<p>When the program stops you should now see variables in the Variables View:</p>
<p><img class="img-fluid" src="/images/articles/2014/intro-to-f4.variablesView.png" alt="Variables View"></p>
<p>That looks much better!</p>
<p>Note that to go back to normal Fantom editing, click the <code>Fantom Perspective</code> button in the top right. (Remember to stop debugging / terminate the program first!)</p>
<p>If you see this message in the Variables tab:</p>
<p class="danger">com.sun.jdi.InternalException: Got error code in reply:35 occurred while retrieving value.</p>
<p>Then simply switch to a different tab, such as the Breakpoints tab, and then back to Variables. You should now see your variables as desired.</p>
<p>It is caused by debugging a Java 8 program. To get around it (if tabbing in and out is too much), install an earlier Java version such as JRE 6 or 7 and set that as your project's JRE System Library.</p>
<h2 id="handyShortcuts">Handy Shortcuts</h2>
<p>Following are some handy keyboard shortcuts that I find myself using all the time.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe ">F3 ................. => Go to source file / definition
Alt + Up/Down ...... => Move current line up / down
Ctrl + Space ....... => Auto-complete / list alternatives
Ctrl + D ........... => Delete line
Ctrl + H ........... => Search for text in files
Ctrl + / ........... => Comment / uncomment selection
Ctrl + Alt + A ..... => Toggle Block Selection Mode
Ctrl + Alt + Up/Down => Copy and paste line (or selection) above or below
Ctrl + Shift + T ... => Open Fantom Type
Ctrl + Shift + R ... => Open file / resource by name
Ctrl + Shift + G ... => Find occurrences of Type / Method / Field
</pre></pre>
<p>Have fun!</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>21 May 2020</em> - Updated content and screenshots to relate to Fantom 1.0.74 (from 1.0.69) and F4 1.1.4 (from 1.1) - by Mike Puckett.</li>
<li><em>23 June 2017</em> - Created a 15 minute video screencast for YouTube.</li>
<li><em>28 August 2016</em> - Updated to relate to F4 v1.1 (from v1.0.2).</li>
<li><em>28 August 2014</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/create-a-mini-web-app-with-bedsheet
Create a Mini Web Application with BedSheet
2016-12-31T00:00:00+01:00
2020-02-24T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="/images/articles/2016/bvw02-create-a-mini-web-app-with-bedsheet.bedsheet.png" alt="BedSheet"> A tutorial on how to create a mini web application with Alien-Factory's <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a> suite of libraries.</p>
<p class="lead">A guide to serving web pages, posting forms, and uploading files.</p>
<p>This article re-writes the same mini web application as in <a href="/articles/create-a-mini-web-app-with-wisp">Create a Mini Web Application with Wisp</a> but using the Alien-Factory BedSheet suite of libraries.</p>
<p>BedSheet is pluggable, customisable, and massively extensible. As such there are many compatible BedSheet libraries that make light work of the common problems in web development.</p>
<p>This article serves to be a light introduction to <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a>, <a href="http://eggbox.fantomfactory.org/pods/afPillow/">Pillow</a>, <a href="http://eggbox.fantomfactory.org/pods/afFormBean/">FormBean</a>, and <a href="http://eggbox.fantomfactory.org/pods/afSlim/">Slim</a>. It assumes the reader is already able to set up a Fantom project, build <code>.pod</code> files, and run code.</p>
<h2 id="contents">Contents</h2>
<ol class="blendChildren" style="list-style-type: decimal;">
<li><a href="#startingBedSheet">Starting BedSheet</a></li>
<li><a href="#servingWebPages">Serving Web Pages</a></li>
<li><a href="#servingWebPagesWithPillow">Serving Web Pages with Pillow</a></li>
<li><a href="#postingForms">Posting Forms</a></li>
<li><a href="#postingFormsWithFormBean">Posting Forms with FormBean</a></li>
<li><a href="#uploadingFilesWithFormBean">Uploading Files with FormBean</a></li>
<li><a href="#tidyUpUseSlim">Tidy Up - Use Slim Templates</a></li>
<li><a href="#bedSheetVsWisp">BedSheet / Wisp Comparison</a></li>
<li><a href="#completeExample">Complete Example</a></li>
</ol>
<p>For reference this tutorial was written with Fantom 1.0.69 and the following set of dependencies. You may copy the <code>depends</code> declaration and paste it in your own <code>build.fan</code>.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">depends = [
"sys 1.0.73 - 1.0",
"afIoc 3.0.8 - 3.0",
"afBedSheet 1.5.14 - 1.5",
"afEfanXtra 2.0.2 - 2.0",
"afPillow 1.2.0 - 1.2",
"afFormBean 1.2.6 - 1.2",
"afSlim 1.3.0 - 1.3",
]
</pre></pre>
<h2 id="startingBedSheet">1. Starting BedSheet</h2>
<p>BedSheet is actually a <code>WebMod</code> like any other and runs under Wisp, and can be setup and run as such. But it also comes with some handy methods to build and run a <code>WispService</code> instance right out of the box. We'll use these!</p>
<p>BedSheet leverages most of its power from being an IoC container. And as such most BedSheet web applications have an <code>AppModule</code> class where all application configuration may be centralised.</p>
<p>Our <code>AppModule</code> will configure the BedSheet <code>Routes</code> service to return the text <code>Hello Mum!</code> for every <code>GET</code> request.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afIoc::Contribute
using afIoc::Configuration
using afBedSheet::BedSheetBuilder
using afBedSheet::Route
using afBedSheet::Routes
using afBedSheet::Text
class Main {
Int main() {
BedSheetBuilder(AppModule#.pod.name).startWisp(8069, true, "dev")
}
}
const class AppModule {
@Contribute { serviceType=Routes# }
Void contributeRoutes(Configuration config) {
config.add(Route(`/*`, Text.fromPlain("Hello Mum!")))
}
}
</pre></pre>
<p>Note the line: <code>BedSheetBuilder(...).startWisp(8069, true, "dev")</code></p>
<p>The <code>true</code> tells BedSheet to start a web proxy to the real instance of the web application. In short this enables BedSheet to automatically re-start your web application every time you recompile your pod. This means you need only refresh your browser to see your latest changes! But note, it only works if your application is compiled into a pod. If running a script, then you must set this parameter to <code>false</code>.</p>
<p>The <code>"dev"</code> tells BedSheet to run in <em>development</em> mode. In development mode the standard 404 and 500 pages are replaced with detailed report pages that help you debug what went wrong. Note that these pages themselves are customisable and you can add your own information to them.</p>
<p>Anyway, running the program and pointing a browser at <code>http://localhost:8069/</code> gives us:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.helloMumPlain.png" alt="Hello Mum! - plain text"></p>
<h2 id="servingWebPages">2. Serving Web Pages</h2>
<p>To serve different pages from different URLs we only need to alter the <code>Routes</code> configuration. Here we'll map the URL <code>`/`</code> to the method <code>IndexPage.onGet()</code>. BedSheet will automatically serve 404 pages for everything else.</p>
<p>BedSheet has the philosophy that route handlers, such as <code>onGet()</code>, shouldn't manipulate the HTTP response directly, but rather return a <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/doc/#responseObjects">response object</a> such as a <code>File</code>, a <code>HttpStatus</code>, or <code>Err</code>. It's then the job of a <em>Response Processor</em> to pipe the object to the HTTP response. This lets the application deal with macro objects without having to worry about detailed specifics of the response.</p>
<p>As such, our <code>onGet()</code> route handler will return a HTML <code>Text</code> object.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">const class AppModule {
@Contribute { serviceType=Routes# }
Void contributeRoutes(Configuration config) {
config.add(Route(`/`, IndexPage#onGet))
}
}
class IndexPage {
Obj onGet() {
Text.fromHtml("<!DOCTYPE html>
<html>
<head>
<title>Wisp Example</title>
</head>
<body>
<h1>Hello Mum!</h1>
</body>
</html>")
}
}
</pre></pre>
<p>Which serves up a page like this:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.helloMumHtml.png" alt="Hello Mum! - html"></p>
<h2 id="servingWebPagesWithPillow">3. Serving Web Pages with Pillow</h2>
<p>Having to manually specify and map URLs to route handlers is pretty tedious, so the <a href="http://eggbox.fantomfactory.org/pods/afPillow/">Pillow</a> library introduces some convention over configuration. It takes each class annotated with <code>@Page</code> and routes a URL to it, based on the class name.</p>
<p>First it drops any "Page" suffix on class names, then it maps the word "Index" to "/", so the class <code>IndexPage</code> is mapped to just <code>/</code>.</p>
<p>And because Pillow does all the routing for us, we can delete the <code>AppModule</code> class.</p>
<p>On to... templating.</p>
<p>Pillow extends <a href="http://eggbox.fantomfactory.org/pods/afEfanXtra/">efanXtra</a> which uses <a href="http://eggbox.fantomfactory.org/pods/afEfan/">efan</a> (Embedded FANtom) templates.</p>
<p>Usually we would keep our page templates as separate files, but for ease of use in this tutorial we're going to use a nifty feature of efanXtra that lets us use fandoc comments as templates! But because we're doing that, we have to also manually set the content type.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afBedSheet::BedSheetBuilder
using afEfanXtra::EfanComponent
using afPillow::Page
class Main {
Int main() {
BedSheetBuilder(AppModule#.pod.name).startWisp(8069, true, "dev")
}
}
const class AppModule { }
** template: efan
**
** <!DOCTYPE html>
** <html>
** <head>
** <title>Wisp Example</title>
** </head>
** <body>
** <h1>Hello Mum!</h1>
** </body>
** </html>
@Page { contentType=MimeType("text/html") }
class IndexPage : EfanComponent { }
</pre></pre>
<h2 id="postingForms">4. Posting Forms</h2>
<p>To post a form we first need to update the <code>IndexPage</code> to incorporate a form:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afBedSheet::BedSheetBuilder
using afEfanXtra::EfanComponent
using afPillow::Page
class Main {
Int main() {
BedSheetBuilder(AppModule#.pod.name).startWisp(8069, true, "dev")
}
}
const class AppModule { }
** template: efan
**
** <!DOCTYPE html>
** <html>
** <head>
** <title>Post Form Example</title>
** </head>
** <body>
** <h1>Post Form</h1>
** <form method='POST' action='/postForm'>
** <label for='name'>Name</label>
** <input type='text' name='name'>
** <br/>
** <label for='beer'>Beer</label>
** <input type='text' name='beer'>
** <br/>
** <input type='submit' />
** </form>
** </body>
** </html>
@Page { contentType=MimeType("text/html") }
class IndexPage : EfanComponent { }
</pre></pre>
<p>To service the URL <code>/postForm</code> we need to add a new Pillow Page class. We'll call it <code>PostFormPage</code>, and in the <code>@Page</code> facet we'll set the HTTP method to <code>POST</code> and explicitly set the URL.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afIoc::Inject
using afEfanXtra::InitRender
using afBedSheet::HttpRequest
** template: efan
**
** <!DOCTYPE html>
** <html>
** <head>
** <title>Post Form Values</title>
** </head>
** <body>
** <p>Hello <b><%= name %></b>, you like <b><%= beer %></b>!</p>
** </body>
** </html>
@Page { httpMethod="POST"; contentType=MimeType("text/html"); url=`/postForm` }
class PostFormPage : EfanComponent {
@Inject HttpRequest httpReq
Str? name
Str? beer
new make(|This| f) { f(this) }
@InitRender
Void initRender() {
name = httpReq.body.form["name"]
beer = httpReq.body.form["beer"]
}
}
</pre></pre>
<p>The above code introduces the <code>@InitRender</code> method which is called before the page renders. Here we are able to extract information from the submitted form and set them as field values.</p>
<p>BedSheet and Pillow use the standard Fantom it-block ctor to inject the <code>HttpRequest</code> object in to the Page class.</p>
<p>Note the efan markup notation of <code><%= xxx %></code> which is able to read field values from the mixin and output them in the template. That's the beauty of efanXtra templates, they are able to interact with their associated class, including calling methods and rendering other templates.</p>
<p>The submitted form produces the following:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.postFormValues.png" alt="Post Form Values"></p>
<h2 id="postingFormsWithFormBean">5. Posting Forms with FormBean</h2>
<p><a href="http://eggbox.fantomfactory.org/pods/formBean/">FormBean</a> is a fantastic library that renders Fantom objects as HTML forms. To use it, we first need to encapsulate our form data as Fantom object:</p>
<pre class="syntax microlight notForIe fantom">using afFormBean::HtmlInput
class BeerDetails {
@HtmlInput Str? name
@HtmlInput Str? beer
}
</pre></pre>
<p>We can then update <code>IndexPage</code> to use FormBean to render our <code>BeerDetails</code> class as a HTML form:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afFormBean::FormBean
** template: efan
**
** <!DOCTYPE html>
** <html>
** <head>
** <title>Post Form Example</title>
** </head>
** <body>
** <h1>Post Form</h1>
** <form method='POST' action='/postForm'>
** <%= formBean.renderBean(null) %>
** <%= formBean.renderSubmit() %>
** </form>
** </body>
** </html>
@Page { contentType=MimeType("text/html") }
class IndexPage : EfanComponent {
@Inject { type=BeerDetails# }
FormBean formBean
new make(|This| f) { f(this) }
}
</pre></pre>
<p>But the clever part about FormBean is that it can validate and reconstitute HTML form data back into a Fantom object instance! Our new <code>PostFormPage</code> does just that and uses the new <code>BeerDetails</code> instance in the template:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">** template: efan
**
** <!DOCTYPE html>
** <html>
** <head>
** <title>Post Form Values</title>
** </head>
** <body>
** <p>Hello <b><%= details.name %></b>, you like <b><%= details.beer %></b>!</p>
** </body>
** </html>
@Page { httpMethod="POST"; contentType=MimeType("text/html"); url=`/postForm` }
class PostFormPage : EfanComponent {
@Inject { type=BeerDetails# }
FormBean formBean
BeerDetails? details
new make(|This| f) { f(this) }
@InitRender
Void initRender() {
formBean.validateHttpRequest()
details = formBean.createBean()
}
}
</pre></pre>
<p>Above is only a basic utilisation of FormBean so it doesn't save us much code. But then again, we only have 2 basic text field inputs. The code would be exactly the same if we had 10 or 20 inputs!</p>
<p>Note that FormBean is able to render all manner of HTML inputs (including select boxes, checkboxes, and radio buttons) and perform client side & server side validation.</p>
<h2 id="uploadingFilesWithFormBean">6. Uploading Files with FormBean</h2>
<p>This is another place where FormBean excels.</p>
<p>First update the HTML <code><form></code> element to submit multipart form-data (essential for file uploads):</p>
<pre class="syntax microlight notForIe "><form method='POST' action='/postForm' enctype='multipart/form-data'></pre></pre>
<p>Then to have FormBean handle the file upload, we only need to add an extra field to <code>BeerDetails</code>:</p>
<pre class="syntax microlight notForIe fantom">class BeerDetails {
@HtmlInput Str? name
@HtmlInput Str? beer
@HtmlInput File? photo
}
</pre></pre>
<p>The code in the <code>PostFormPage</code> class stays the same. We just need to add a couple more lines to the template to output the uploaded file:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">** template: efan
**
** <!DOCTYPE html>
** <html>
** <head>
** <title>Post Form Values</title>
** </head>
** <body>
** <p>
** Hello <b><%= details.name %></b>, you like <b><%= details.beer %></b>!
** The photo <b><%= details.photo.name %></b> looks like:
** </p>
** <img src="data:<%= details.photo.mimeType %>;base64,<%= details.photo.readAllBuf.toBase64 %>">
** </body>
** </html>
@Page { httpMethod="POST"; contentType=MimeType("text/html"); url=`/postForm` }
class PostformPage : EfanComponent {
@Inject { type=BeerDetails# }
FormBean formBean
BeerDetails? details
new make(|This| f) { f(this) }
@InitRender
Void initRender() {
formBean.validateHttpRequest()
details = formBean.createBean()
}
}
</pre></pre>
<p>The net result is that we end up with this web page:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.uploadFileValues.png" alt="Upload File Values"></p>
<h2 id="tidyUpUseSlim">7. Tidy Up - Use Slim Templates</h2>
<p>HTML is clunky. It has too much punctuation making it hard to type. And if you miss out an end tag or miss type a void tag, you'll never know until it doesn't render in Internet Explorer or some other obscure browser. Just like it's Javascript counterpart, it is functional but ugly.</p>
<p>So enter <a href="http://eggbox.fantomfactory.org/pods/afSlim/">Slim</a>. Based on the Ruby library of the same name, Slim reduces HTML syntax without becoming cryptic. It is indentation driven and has CSS style shortcuts for <code>#id</code> and <code>.class</code> attributes.</p>
<p>Slim is IoC aware, so just by adding the Slim pod to our project it configures efanXtra to recognise slim templates. That means we can jump straight in and re-write our <code>IndexPage</code> as:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">** template: slim
**
** doctype html
** html
** head
** title Post Form Example
** body
** h1 Post Form
** form (method='POST' action='/postForm' enctype='multipart/form-data')
** == formBean.renderBean(null)
** == formBean.renderSubmit()
@Page { contentType=MimeType("text/html") }
class IndexPage : EfanComponent { ... }
</pre></pre>
<p>See <a href="#completeExample">below</a> for the <code>PostformPage</code> slim template.</p>
<h2 id="bedSheetVsWisp">8. BedSheet / Wisp Comparison</h2>
<p>Comparing the <a href="#completeExample">BedSheet code</a> below with the <a href="/articles/create-a-mini-web-app-with-wisp#completeExample">wisp code</a> in the previous article heralds about a 40% reduction in code (~100 lines vs ~60 lines).</p>
<p>There's also a better separation of concerns with respect to page classes, data classes, and templates.</p>
<p>Not to mention the added benefits of BedSheet with regards to development; no application re-starts (just refresh the browser page), extensive error and 404 handling, and dedicated testing frameworks.</p>
<p>And now that you're in the land of BedSheet, a world of potential has just opened up; add site maps, RSS feeds, and asset caching in seconds! See the <a href="http://eggbox.fantomfactory.org/pods/?tags=web">Eggbox Pod Repository</a> for details.</p>
<h2 id="completeExample">9. Complete Example</h2>
<p>Below is the complete BedSheet example that:</p>
<ul class="blendChildren">
<li>Uses BedSheet and IoC as the web application container</li>
<li>Uses Pillow to route URLs to page classes</li>
<li>Uses FormBean to render HTML forms and handle file uploads</li>
<li>Uses Slim to render concise HTML</li>
</ul>
<p>Note the BedSheet code below is about half the size of the plain Wisp example.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afIoc::Inject
using afBedSheet::BedSheetBuilder
using afBedSheet::HttpRequest
using afEfanXtra::InitRender
using afEfanXtra::EfanComponent
using afPillow::Page
using afFormBean::FormBean
using afFormBean::HtmlInput
class Main {
Int main() {
BedSheetBuilder(IndexPage#.pod.name).startWisp(8069, true, "dev")
}
}
** template: slim
**
** doctype html
** html
** head
** title Post Form Example
** body
** h1 Post Form
** form (method='POST' action='/postForm' enctype='multipart/form-data')
** == formBean.renderBean(null)
** == formBean.renderSubmit()
@Page { contentType=MimeType("text/html") }
class IndexPage : EfanComponent {
@Inject { type=BeerDetails# }
FormBean formBean
new make(|This| f) { f(this) }
}
** template: slim
**
** doctype html
** html
** head
** title Post Form Values
** body
** p
** Hello <b>${details.name}</b>, you like <b>${details.beer}</b>!
** The photo <b>${details.photo.name}</b> looks like:
** img (src="data:${details.photo.mimeType};base64,${details.photo.readAllBuf.toBase64}")
@Page { httpMethod="POST"; contentType=MimeType("text/html"); url=`/postForm` }
class PostFormPage : EfanComponent {
@Inject { type=BeerDetails# }
FormBean formBean
BeerDetails? details
new make(|This| f) { f(this) }
@InitRender
Void initRender() {
formBean.validateHttpRequest()
details = formBean.createBean()
}
}
class BeerDetails {
@HtmlInput Str? name
@HtmlInput Str? beer
@HtmlInput File? photo
}
</pre></pre>
<p>Have fun!</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>24 Feb 2020</em> - Updated pods and corresponding code to latest versions.</li>
<li><em>31 Dec 2016</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/zorin-network-install
Zorin OS Network Install
2019-01-05T00:00:00+01:00
2019-01-05T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="/images/articles/2019/zorin-network-install.sonyVaioZ1.png" alt="Sony Vaio PCG-Z1XSP" width="192" height="192"> I had this really old laptop (Sony Vaio PCG-Z1XSP). It's a sleek looking computer and packed a powerful processing punch in its day. It was well loved, well used, and well... to be honest, it had spent the past 10 years on a shelf! But I had recent need to play with a Linux distribution and thought it was time to breath life back into the old beast!</p>
<p>I needed a Debian based distro, so naturally went for the no-fuss Ubuntu.</p>
<p>My laptop is pretty old, it runs Windows XP. The first thing I wanted to do is prove that Linux could actually run on it and had drivers for the display and wifi. Only here's my first problem... The BIOS had no support for booting from USB devices and the internal DVD was knackered.</p>
<p>I found that out after unsuccessfully attempting installs via both media. (I lost my first few hours here.)</p>
<p>I then came across <a href="https://help.ubuntu.com/community/Wubi">Wubi</a>:</p>
<blockquote>
<p>The Windows-based Ubuntu Installer (Wubi) allows you to install and uninstall Ubuntu from within Microsoft Windows. It lets a Microsoft Windows user try Ubuntu without risking any data loss due to disk formatting or partitioning.</p>
</blockquote>
<p>Perfect!</p>
<p>Only despite having a formal page on the <a href="https://help.ubuntu.com/community/Wubi">Ubuntu website</a> and their <a href="https://wiki.ubuntu.com/WubiGuide">wiki</a>, <a href="https://www.lifewire.com/wubi-linux-installation-program-2201175">Ubuntu don't actually support Wubi</a> anymore and the SourceForge link is dead. But the <a href="https://github.com/hakuna-m/wubiuefi">GitHub</a> page is still going strong and supports all the <a href="https://github.com/hakuna-m/wubiuefi/releases">latest Ubuntu versions</a>!</p>
<p>Wubi worked great and logged me into a live Ubuntu installation. Only it was slow. Dog slow. Even the mouse pointer lagged. And don't expect anything to happen for at least 30 seconds if you happen to double click anywhere.</p>
<p>I needed a lightweight version of Ubuntu. I then found out that Ubuntu also comes in <a href="http://www.linuxandubuntu.com/home/top-alternative-linux-distributions-to-windows-xp">other flavours</a>. <a href="https://lubuntu.net/">Lubuntu</a> is supposedly really good for older computers. Then there's <a href="https://www.linuxmint.com/">Linux Mint Xfce</a> which is the (popular) Linux Mint but with the lightweight Xfce Desktop environment which also makes it good for old computers.</p>
<p>Then I found <a href="https://zorinos.com/">Zorin OS</a>, which is specifically aimed at replacing Windows, is itself a <a href="https://lubuntu.me/zorin-os-6-lite/">flavour of Lubuntu</a>, and comes in a <a href="https://zorinos.com/download/lite/">Zorin Lite</a> version sporting the lightweight <a href="https://askubuntu.com/a/990665/188406">Xfce desktop</a>. Choice made.</p>
<p>(As a bonus Zorin is also a cool villain from <a href="https://jamesbond.fandom.com/wiki/Max_Zorin">James Bond</a>. Man, this OS is a no-brainer!)</p>
<p>But Wubi only supports pure Ubuntu distributions (I hadn't seen <a href="https://www.youtube.com/watch?v=Jtzm4DOdrKw">this video</a>). So how do I install it on my laptop?</p>
<p><a href="https://neosmart.net/EasyBCD/">EasyBCD</a> lets your Windows machine boot from an .ISO file - awesome! I downloaded the latest 2.4 version. It crashed on startup. But many (older) blogs swore by it!? I tried hunting for downloads of older versions. v2.3 crashed too. v2.0 gave a strange error that it couldn't find the correct boot records. v2.1 then explained itself - it only works with the newer Win7 boot loader. Thwarted.</p>
<p>Next I looked at <a href="https://unetbootin.github.io/">UNetbootin</a> as it could create a Live-USB type installer with dual boot, but on my <em>local hard-drive</em> - it looked promising. But it didn't support Zorin out of the box and just hinted that it <em>might</em> work! Yeah, I didn't really want to chance it as it didn't say anything about handling the older WinXP boot loader - and I didn't want to brick my laptop.</p>
<p>Wanting to look at Zorin to see if it's worth the effort I used <a href="https://rufus.ie/en_IE.html">Rufus</a> to create a boot USB. Rufus was no fuss and just worked. Trying it out on a different machine, it looked good. But how do I install it on my laptop?</p>
<p>USB install? No. DVD install? No. Hard-drive install? Dubious, and no. Which just left Network install.</p>
<p>Bugger. Never in a million years will this work!</p>
<p>Then I found <a href="https://www.vercot.com/~serva/">Serva</a> and there was new hope. Serva is actually bloody brilliant and, despite the problems below, is well worth supporting and buying, even though there's a free community version. The guy has obviously put many, many hours into updating and tweaking his software - and continues to do so.</p>
<p>There is a LOT to read on setting up Serva - and a lot of it is really useful. But boy, I really wished there was quick start guide! There is <a href="https://www.vercot.com/~serva/an/WindowsPXE1.html">this page</a> that explains how to do a network install of Windows. And then there is <a href="https://www.vercot.com/~serva/an/NonWindowsPXE3.html">this page</a> that expands on the first, with explainations for doing the same with Ubuntu.</p>
<p>So let me sum up what I did. On starting Serva, edit the settings and:</p>
<p>Under the TFTP tab:</p>
<ul class="blendChildren">
<li>enable the TFTP Server</li>
<li>set a path to some newly created <code>SERVA_REPO</code> directory</li>
</ul>
<p>Under the DHCP tab:</p>
<ul class="blendChildren">
<li>enable the <code>proxyDHCP</code></li>
<li>tick the <code>BINL</code> service add-on</li>
</ul>
<p>Expand the Zorin .ISO to:</p>
<pre class="syntax microlight notForIe ">+- SERVA_REPO/
+- NWA_PXE/
+- Zorin/
</pre></pre>
<p>Copy the Zorin <a href="https://www.vercot.com/~serva/an/NonWindowsPXE3.html#code-snippet-22">ServaAsset.inf</a> file to <code>SERVA_REPO/NWA_PXE/Zorin/</code>.</p>
<p>Download the specified <a href="https://www.vercot.com/~serva/an/NonWindowsPXE3.html#code-snippet-22">INIT_XXX.GZ</a> and copy it (don't unzip it) to <code>SERVA_REPO/NWA_PXE/Zorin/casper/</code>.</p>
<p>Create a new Windows user called <code>serva</code> with a password of <code>avres</code> (serva backwards!), share the <code>NWA_PXE/</code> directory as <code>NWA_PXE_SHARE</code> (go to folder <code>Properties -> Sharing -> Advanced Sharing</code> to change the share name), and give the <code>serva</code> user some read access rights.</p>
<p>Plug your old laptop into wired Ethernet connection (your Serva computer can be on the wifi), configure it to boot from the network and turn it on...</p>
<p>...and the laptop connects to Serva and configures itself through the DHCP proxy; this is magic to me! Serva offers up boot options from the <code>ServaAsset.inf</code> files. You select one on the laptop and the Pre-boot eXecution Environment (PXE) then downloads the named Linux kernel files through TFTP, including the <code>INIT_XXX.GZ</code> file. This is a (gzipped) hand-rolled script that the Serva guy (Patrick Masotta) wrote - it does a lot of network stuff and mounts the Windows shared directory under <code>cdrom/</code>.</p>
<p>And from there Zorin can just install itself as if booting from a CD. Magic!</p>
<p>Only here's what actually happens...</p>
<pre class="syntax microlight notForIe ">DHCP... check.
TFTP... check.
WARNING: PAE disabled.
Use parameter 'forcepae' to enable at your own risk!
This kernel requires the following features not present on the CPU.
Unable to boot - please use kernel appropriate for your CPU.
As soon as the kernel files run,
</pre></pre>
<p>What the fudge is this!?</p>
<p><a href="https://wiki.ubuntu.com/Lubuntu/AdvancedMethods#Pentium_M_and_Celeron_M">As it turns out</a> PAE is some sort of CPU enhancement required by all modern Ubuntu systems. Most really old processors were already PAE enabled but forgot to check a flag to inform the residing OS. So my installer crashed out not knowing if my CPU has PAE or not.</p>
<p>There is a <code>forcepae -- forcepae</code> option you can set (yes, you have to set it <a href="https://help.ubuntu.com/community/BootOptions/before--after">twice</a> - just to make sure!). But where!?</p>
<p>Turns out (after a lucky guess) I can just append it in the <code>ServaAsset.inf</code> file:</p>
<pre class="syntax microlight notForIe ">append_bios = ... NFSOPTS=-ouser=serva,pass=avres,sec=ntlm,vers=1.0,ro ip=dhcp ro ipv6.disable=1 forcepae -- forcepae</pre></pre>
<p>Note I only bother changing the <code>append_bios</code> line as I know my old laptop doesn't support the newer <code>EFI/UEFI</code> protocol.</p>
<p>Restart Serva, restart Laptop, ... Yes! I get further.</p>
<p>But then...</p>
<pre class="syntax microlight notForIe ">mount error(121): Remote I/O Error</pre></pre>
<p>There's a problem mounting my shared network drive. The error code is <a href="https://stackoverflow.com/questions/30054880/what-does-cifs-mount-failed-w-return-code-111-indicate">next to useless</a> but there's lots of talk on the <a href="https://www.vercot.com/~serva/an/NonWindowsPXE3.html#troubleshooting">Serva website</a> and <a href="https://askubuntu.com/questions/510454/anybody-have-experience-with-ubuntu-serva">StackOverflow</a> about tweaking the <code>NFSOPTS</code> to make sure they're inline with what your Windows machine is dishing out.</p>
<p>I spent hours methodically tweaking these options and then restarting Serva and my laptop. I finally spotted two things:</p>
<p>1). When the network install gives up the ghost, it gives a Linux BusyBox bash prompt. So you can type in the mount command without restarting anything - a much faster method of debugging!</p>
<p>For instance, I would type (your IP address and dir name would be different):</p>
<pre class="syntax microlight notForIe ">mount.cifs //192.168.1.139/NWA_PXE_SHARE/Zorin-OS-12.4-Lite-32 /cdrom -ouser=serva,pass=avres,sec=ntlm,vers=1.0,ro</pre></pre>
<p>2). All the errors in the docs and blogs speak of:</p>
<pre class="syntax microlight notForIe ">mount error(13): Permission Denied</pre></pre>
<p>Where I was actually getting:</p>
<pre class="syntax microlight notForIe ">mount error(121): Remote I/O Error</pre></pre>
<p>The only thing my Google foo could find on the topic was a short <a href="https://blog.oliver-arp.de/?p=159">German blog post</a> which suggested updating the following registry settings:</p>
<pre class="syntax microlight notForIe ">HKLM \ SYSTEM \ CurrentControlSet \ Control \ Session Manager \ Memory Management \ LargeSystemCache = 1
HKLM \ SYSTEM \ CurrentControlSet \ Services \ LanmanServer \ Parameters \ Size = 3</pre></pre>
<p>I'm not a fan of randomly changing registry settings, but I was running out of options. So I did. I restarted my <code>Server</code> service and the next <code>mount.cifs</code> just worked!</p>
<p>Alright! Restart Serva, restart the laptop, and...</p>
<pre class="syntax microlight notForIe ">mount error(112): host is down</pre></pre>
<p>Damn it. I ran my <code>mount.cifs</code> command again and it worked. Confused, I resigned to having to do a lot more cmd prompt typing. So to make my life easier I renamed the <code>Zorin-OS-12.4-Lite-32</code> directory to just <code>Zorin</code> and tried again.</p>
<p>And then... the Zorin OS booted up a live instance and presently me with a desktop.</p>
<p><img class="img-fluid" src="/images/articles/2019/zorin-network-install.desktop.jpg" alt="Zorin Desktop"></p>
<p>Hallelujah - it worked!</p>
<p>Now if only I could remember what I wanted Linux for in the first place?</p>
http://www.alienfactory.co.uk/articles/fantom-redux
Fantom Redux
2019-01-02T00:00:00+01:00
2019-01-02T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="/images/articles/2019/fantom-redux.icon.png" alt="Redux logo" width="128" height="128"> I happened across this little javascript library called <a href="https://redux.js.org/">Redux</a>. The link I followed touted it as a silver bullet "backend" library along with React. Now, I despise all these modern javascript <em>fad</em> libraries. So, like a hauty teenager intent on getting outraged by reading facebook posts, I decided to have a closer look...</p>
<p>The Redux website liberally mentions "facebook" <em><code><hate></code></em> as an inspiration, and CPQS <em><code><fad></code></em>. The React <em><code><more fad, more hate></code></em> library is it's biggest consumer. Ooo, I hated it all already. I was getting really angry and worked up, just as intended!</p>
<p>But then I start to notice words like, "immutability", "pure functions", and "single source of truth" and my interest begins to perk up. I read "time-traveling state machine" and I'm intrguied. I then see the teeny tiny 9 method <a href="https://redux.js.org/api/api-reference">public API</a> and I'm hooked!</p>
<p>I immediately skim read the entire 7 page introdution. <em><code>I get it</code></em>, <em><code>I understand it</code></em>, but most importantly <em><code>I WANT IT!</code></em></p>
<p>So here's Redux in a nutshell as I see it:</p>
<blockquote>
<p>Redux is a methodology for representing your domain model with immutable data. This state can then only be updated by applying small incremental actions. These actions themselves are also immutable and idempotent (pure functions) - which makes your entire system completely deterministic to point where you can even roll your domain back in time!</p>
</blockquote>
<p>Yes, Redux is just <a href="https://martinfowler.com/eaaDev/EventSourcing.html">Event Sourcing</a> at heart, but it's also more than that. It's not just an term or an idea, it's also a framework and methodology for implementing it.</p>
<p>Anyway, I was later soaking in a hot bath and found myself pondering over the design implications of implementing a Redux application and realised I'd already built it! <em> <strong>"Eureka!"</strong> </em></p>
<p>The <a href="https://stackhub.org/">StackHub</a> web application, with its strict adherence to update actions on the domain objects backed by a caching mechanism... is Redux! I just hadn't generalised the code or abstracted out a library. It may be hard coded in the source, but the design ideas were all there!</p>
<p>I was excited again. So much so I felt the need to rewrite Redux in Fantom. I figured it should be easy as Fantom is type safe and has immutability baked in to its core - it lends itself to Redux far more than javascript ever will.</p>
<p>I quickly bashed out a fully functioning and complete bare bones implementation of Redux... in about 25 lines of Fantom code - which runs a faithful representation of the basic <a href="https://redux.js.org/basics/store#dispatching-actions">Redux Todo example</a>. And here it is:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">// ---- Redux ----
class Store {
private Obj? rootState
const Reducer rootReducer
StoreListener[] listeners := StoreListener[,]
new make(Obj? rootState, Reducer rootReducer) {
this.rootState = rootState
this.rootReducer = rootReducer
}
Obj? getState() {
rootState
}
Void dispatch(Action action) {
rootState = rootReducer.reduce(rootState, action).toImmutable
listeners.each { it.actionDispatched(this, action) }
}
|->| subscribe(StoreListener listener) {
listeners.add(listener)
return |->| { listeners.remove(listener) }
}
}
const mixin StoreListener {
abstract Void actionDispatched(Store store, Action action)
}
const mixin Action { }
const mixin Reducer {
abstract Obj? reduce(Obj? state, Action action)
}
</pre></pre>
<p>Next I'll walk you through the Todo example. It's great for understanding how a Redux application works and how it's all wired together.</p>
<p>The idea is to create a domain model that lets you create a Todo list, mark them complete, and update some view data. But all using immutable state.</p>
<p>I believe the following type safe Fantom implementation of the Todo application is a <em>lot easier</em> to follow and understand than the corresponding javascript one.</p>
<p>So here goes:</p>
<h3 id="1.DefineYourDomainModel">1. Define your domain model</h3>
<p>You need to first define what data your application will hold. Note how all the classes are <code>const</code> and immutable <em>(thank you Fantom!)</em>. This is the key to a deterministic Redux application.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">// ---- State ----
const class AppRootState {
const Str filter := "off"
const Todo[] todos := Todo[,]
new make(|This|? f := null) { f?.call(this) }
override Str toStr() { "${filter} - ${todos}" }
}
const class Todo {
const Str text
const Bool isComplete
new make(|This| f) { f(this) }
override Str toStr() { (isComplete ? "COMPLETE" : "TODO") + ": ${text}" }
}
</pre></pre>
<h3 id="2.DefineYourActions">2. Define your actions</h3>
<p>Actions are write operations that will be performed on your domain. Javascript Redux uses strings to identify unique actions. Here in Fantom land we can use type safe classes instead.</p>
<p>The <code>Action</code> mixin is just used as a marker interface.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">// ---- Actions ----
const class AddTodoAction : Action {
const Str text
new make(Str text) {
this.text = text
}
}
const class ToggleTodoAction : Action {
const Int todoId
new make(Int todoId) {
this.todoId = todoId
}
}
const class SetVisibilityFilterAction : Action {
const Str filter
new make(Str filter) {
this.filter = filter
}
}
</pre></pre>
<h3 id="3.WriteYourReducers">3. Write your reducers</h3>
<p>This is the actual implementation of how you update the immutable domain model.</p>
<p>You tend to write a <code>Reducer</code> for each part of the domain model that needs updating. So we have three, one for the root object, one for the view state, and one for updating the <code>Todo</code> entity.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">// ---- Reducers ----
const class AppRootReducer : Reducer {
override Obj? reduce(Obj? state, Action action) {
appState := (AppRootState) state
return AppRootState {
it.filter = VisibilityReducer().reduce(appState.filter, action)
it.todos = TodoReducer().reduce(appState.todos, action)
}
}
}
const class VisibilityReducer : Reducer {
override Obj? reduce(Obj? state, Action action) {
filter := (Str) state
if (action is SetVisibilityFilterAction)
filter = ((SetVisibilityFilterAction) action).filter
return filter
}
}
const class TodoReducer : Reducer {
override Obj? reduce(Obj? state, Action action) {
todos := (Todo[]) state
if (action is AddTodoAction) {
addTodoAction := (AddTodoAction) action
newTodo := Todo {
it.text = addTodoAction.text
it.isComplete = false
}
todos = todos.rw
todos.add(newTodo)
}
if (action is ToggleTodoAction) {
toggleTodoAction := (ToggleTodoAction) action
oldTodo := todos[toggleTodoAction.todoId]
newTodo := Todo {
it.text = oldTodo.text
it.isComplete = !oldTodo.isComplete
}
todos = todos.rw
todos[toggleTodoAction.todoId] = newTodo
}
return todos
}
}
</pre></pre>
<p>Yes, the word <a href="https://redux.js.org/glossary#reducer">Reducer</a> is a terrible, non-descriptive word. The Redux documentation contains paragraphs of text that attempt to justify the use of the word "reduce", which just tells me even they know it's stoopid name!</p>
<h3 id="4.(Optional)WriteSomeListeners">4. (Optional) Write some listeners</h3>
<p>Listeners receive callback events when an action is dispatched / invoked. The example uses them to print out the state of the domain after each action.</p>
<pre class="syntax microlight notForIe fantom">// ---- Listeners ----
const class EchoListner : StoreListener {
override Void actionDispatched(Store store, Action action) {
echo("${action.typeof.name} -> ${store.getState}")
}
}
</pre></pre>
<p>And here is the main example that invokes all of the above. You can see how, line-for-line, it is identical the <a href="https://redux.js.org/basics/store#dispatching-actions">javascript version</a>.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">const class TodoExample {
static Void main(Str[] args) {
store := Store(AppRootState(), AppRootReducer())
echo(store.getState)
unsubscribe := store.subscribe(EchoListner())
store.dispatch(AddTodoAction("Learn about actions"))
store.dispatch(AddTodoAction("Learn about reducers"))
store.dispatch(AddTodoAction("Learn about store"))
store.dispatch(ToggleTodoAction(0))
store.dispatch(ToggleTodoAction(1))
store.dispatch(SetVisibilityFilterAction("showCompleted"))
unsubscribe()
echo(store.getState)
}
}
</pre></pre>
<p>Which when run, prints out:</p>
<pre class="syntax microlight notForIe ">off - [,]
AddTodoAction -> off - [TODO: Learn about actions]
AddTodoAction -> off - [TODO: Learn about actions, TODO: Learn about reducers]
AddTodoAction -> off - [TODO: Learn about actions, TODO: Learn about reducers, TODO: Learn about store]
ToggleTodoAction -> off - [COMPLETE: Learn about actions, TODO: Learn about reducers, TODO: Learn about store]
ToggleTodoAction -> off - [COMPLETE: Learn about actions, COMPLETE: Learn about reducers, TODO: Learn about store]
SetVisibilityFilterAction -> showCompleted - [COMPLETE: Learn about actions, COMPLETE: Learn about reducers, TODO: Learn about store]
showCompleted - [COMPLETE: Learn about actions, COMPLETE: Learn about reducers, TODO: Learn about store]
</pre></pre>
<p>Admittedly, as the above 100 line example shows, most of the work is done by your application. You have to write the meat of Redux yourself. So the real challenge is yet to come; to see how much boilerplate code can be cut out.</p>
<p>And I already have ideas for that...</p>
<p>Stay tuned!</p>
http://www.alienfactory.co.uk/articles/mongodb-scramsha1-over-sasl
MongoDB SCRAM-SHA-1 over SASL
2015-11-01T00:00:00+01:00
2018-05-26T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead">I recently implemented SCRAM-SHA-1 over SASL for <a href="http://eggbox.fantomfactory.org/pods/afMongo/">Fantom's MongoDB driver</a> so it could authenticate against MongoDB v3 databases.</p>
<p class="lead">Much to my surprise, for such a massive breaking change to MongoDB drivers, there's next to nothing available that succinctly explains how it works. Not a sausage!</p>
<p>What I did find though, was a list of specifications and documentation that was as cryptic as the authentication mechanism itself!</p>
<p>By scrutinising every word in the documentation, and by reading the source code for a native <a href="https://github.com/alttagil/mongodb-erlang/blob/develop/src/core/mc_auth_logic.erl">Erlang driver</a>, I was finally able to figure it out.</p>
<p>So now I present to you, what would have been extremely helpful to me, a fully worked authentication conversation with MongoDB for SCRAM-SHA-1 over SASL.</p>
<h2 id="overview">Overview</h2>
<p>All the authentication documentation for MongoDB v3.x talks of <code>SCRAM-SHA-1</code>, or Salted Challenge Response Authentication Mechanism (SCRAM) with SHA-1. SCRAM defines how to encode an authentication message to send to the server. It uses the <code>PBKDF2</code> algorithm from the Public-Key Cryptography Standards (PKCS).</p>
<p>SASL, or Simple Authentication and Security Layer, then defines a protocol of how to send / receive these authentication messages.</p>
<p>These SASL messages are then wrapped up in MongoDB BSON documents and sent as database commands in the usual MongoDB driver manner.</p>
<p>If you want the low down on all of the above, here are links to the relevant parts of the specifications:</p>
<ul class="blendChildren">
<li><a href="https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst#scram-sha-1">MongoDB Driver Authentication with SCRAM-SHA-1</a></li>
<li><a href="http://tools.ietf.org/html/rfc5802#page-8">RFC5802 - Salted Challenge Response Authentication Mechanism (SCRAM) - Sec. 3 Algorithm Overview</a></li>
<li><a href="http://tools.ietf.org/html/rfc4422#section-3">RFC4422 - Simple Authentication and Security Layer (SASL) - Sec. 3 The Authentication Exchange</a></li>
<li><a href="http://tools.ietf.org/html/rfc2898#section-5.2">RFC2898 - Password-Based Cryptography Specification - Sec. 5.2 PBKDF2</a></li>
</ul>
<p>Note that the MongoDB specification above was bloody hard to track down! It does have a sample client / server conversation, but it makes no attempt to explain how it calculated at the given values. For consistency, this example uses the same the parameters, but shows how they were calculated.</p>
<p>The example conversation assumes the following values:</p>
<ul class="blendChildren">
<li>Username: <code>user</code></li>
<li>Password: <code>pencil</code></li>
<li>Database: <code>test</code></li>
</ul>
<p>Note that all code listed here is written in <a href="http://fantom-lang.org/">Fantom</a>.</p>
<h2 id="step1">Step 1</h2>
<p>Send an authentication request to the server.</p>
<p>In the request we name the user we wish to authenticate as, and a nonce. The nonce is random sequence of characters and should be of cryptographic strength.</p>
<pre class="syntax microlight notForIe fantom">clientNonce := ... crypto strength random characters ...
// --> fyko+d2lbbFgONRv9qkxdawL
clientFirstMsg := "n=${userName},r=${clientNonce}"
// --> n=user,r=fyko+d2lbbFgONRv9qkxdawL
</pre></pre>
<p>Send a command to the test database ( <code>test.$cmd</code> ) consisting of the following BSON document. Note that <code>n,,</code> is constant and in some literature is referred to as <code>GS2 Header</code>.</p>
<pre class="syntax microlight notForIe fantom">{
"saslStart" : 1,
"mechanism" : "SCRAM-SHA-1",
"payload" : binary("n,," + clientFirstMsg),
"autoAuthorize" : 1
}
</pre></pre>
<p>In all these messages, the <code>payload</code> field is a BSON binary object, but the content is always just ASCII characters.</p>
<p>The server should respond with the following BSON document:</p>
<pre class="syntax microlight notForIe fantom">{
"conversationId" : 1,
"payload" : binary("r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000"),
"done" : false,
"ok" : 1
}
</pre></pre>
<p>From which we can derive the following variables:</p>
<pre class="syntax microlight notForIe fantom">conversationId := 1
serverFirstMsg := "r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000"
serverNonce := "fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE"
serverSalt := "rQ9ZY3MntBeuP3E1TDVC4w=="
serverIterations := 10000
</pre></pre>
<h2 id="step2">Step 2</h2>
<p>Send an encoded message that proves we have the password. This is hard part.</p>
<p>First we create a string hash of the normalised password.</p>
<pre class="syntax microlight notForIe fantom">hashedPassword := Buf().print("${userName}:mongo:${password}").toDigest("MD5").toHex
// --> "1c33006ec1ffd90f9cadcbcc0e118200"
</pre></pre>
<p>Next we do the cryptographic stuff. Most languages have classes or libraries for doing this. Fantom uses Java's <a href="https://docs.oracle.com/javase/7/docs/api/javax/crypto/spec/PBEKeySpec.html">javax.crypto.spec.PBEKeySpec</a>.</p>
<pre class="syntax microlight notForIe fantom">dkLen := 20 // the size of a SHA-1 hash
saltedPassword := Buf.pbk("PBKDF2WithHmacSHA1", hashedPassword, Buf.fromBase64(serverSalt), serverIterations, dkLen)
// --> 6a bd 37 85 0d a6 e3 27 df 8d c5 af d4 30 79 10 52 f9 24 99
</pre></pre>
<p>In the next string, <code>clientFinalNoPf</code> (client final no proof), note that <code>biws</code> is a constant and is the just the string <code>"n,,"</code> Base64 encoded. In some literature this is referred to as the <code>GS2 Header</code>.</p>
<p>The string <code>"Client Key"</code> is also constant and is used as a default message to be hashed by the (salted) password.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">clientFinalNoPf := "c=biws,r=${serverNonce}"
// --> "c=biws,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE"
authMessage := "${clientFirstMsg},${serverFirstMsg},${clientFinalNoPf}"
// --> "n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000,c=biws,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE"
clientKey := Buf().print("Client Key").hmac("SHA-1", saltedPassword)
// --> 6e ca 60 b8 b0 46 77 1f c7 17 40 92 de 6e 7e 83 78 59 b3 56
storedKey := clientKey.toDigest("SHA-1")
// --> a7 9c fa 9f b5 2d a9 ff a9 2c 19 1a 78 99 38 4f 77 81 38 e0
clientSignature := Buf().print(authMessage).hmac("SHA-1", storedKey)
// --> 5e e7 f3 48 ab 9d ee 7b 9b 87 7c ae 7f 07 07 a2 20 78 73 70
clientProof := xor(clientKey, clientSignature)
// --> 30 2d 93 f0 1b db 99 64 5c 90 3c 3c a1 69 79 21 58 21 c0 26
clientFinal := "${clientFinalNoPf},p=${clientProof.toBase64}"
// --> "c=biws,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,p=MC2T8BvbmWRckDw8oWl5IVghwCY="
</pre></pre>
<p>The <code>xor()</code> method is an annoying little thing where each byte of each input have to be xored together. It is not a native Fantom function, but instead a method you have to write yourself.</p>
<p>It's the <code>clientFinal</code> string that we send as the payload in the next message to the server.</p>
<pre class="syntax microlight notForIe fantom">{
"saslContinue" : 1,
"conversationId" : conversationId,
"payload" : binary(clientFinal)
}
</pre></pre>
<p>The server should then respond with:</p>
<pre class="syntax microlight notForIe fantom">{
"conversationId" : 1,
"payload" : binary("v=UMWeI25JD1yNYZRMpZ4VHvhZ9e0="),
"done" : false,
"ok" : 1
}
</pre></pre>
<p>Now it is our chance to validate the server and check that <em>it</em> also knows the user's password.</p>
<p>Note the string <code>"Server Key"</code> is constant and is used as a default message to be hashed by the (salted) password.</p>
<pre class="syntax microlight notForIe fantom">serverKey := Buf().print("Server Key").hmac("SHA-1", saltedPassword)
// --> 95 1a d5 1f 2a 8c 5f e3 8e a8 6b e9 72 fb fd 6a 79 40 f0 84
serverSignature := Buf().print(authMessage).hmac("SHA-1", serverKey).toBase64
// --> "UMWeI25JD1yNYZRMpZ4VHvhZ9e0="
</pre></pre>
<p>Note that <code>serverSignature</code> should equal the value sent by the server.</p>
<h2 id="step3">Step 3</h2>
<p>Acknowledge and end the conversation.</p>
<p>Next we send a quick message to the server to finish up. Note that <code>payload</code> is an empty binary object.</p>
<pre class="syntax microlight notForIe fantom">{
"saslContinue" : 1,
"conversationId" : conversationId,
"payload" : binary()
}
</pre></pre>
<p>To which the server should respond with:</p>
<pre class="syntax microlight notForIe fantom">{
"conversationId" : 1,
"payload" : binary(),
"done" : true,
"ok" : 1
}
</pre></pre>
<p>Note that <code>done</code> is now <code>true</code> and that signals the end of the authentication conversation.</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>1 November 2015</em> - Original article.</li>
<li><em>26 May 2018</em> - Added notes on <code>"Client Key"</code> and <code>"Server Key"</code> being constant.</li>
</ul>
http://www.alienfactory.co.uk/articles/skyspark-scram-over-sasl
SCRAM over SASL for SkySpark v3
2016-12-26T00:00:00+01:00
2018-05-26T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead">I recently implemented SCRAM (SHA-256) over SASL for <a href="https://skyfoundry.com/skyspark/">SkySpark v3</a> so I could use the REST API.</p>
<p class="lead">SkySpark v3 sports a new authentication algorithm that must be used to communicate with it. But alas there is not much available that succinctly explains how it works.</p>
<p>What I did find though, was a bucket load of specifications and documentation that were as cryptic as the authentication mechanism itself!</p>
<p>So now I present to you, what would have been extremely helpful to me, a fully worked authentication conversation with SkySpark for SCRAM (SHA-256) over SASL.</p>
<p><img class="img-fluid" src="/images/articles/2016/skyspark-scram-over-sasl.ogimage.jpg" alt="Random Stock Photo!"></p>
<h2 id="overview">Overview</h2>
<p>All the authentication documentation for SkySpark v3 talks of <code>SCRAM SHA-256</code>, or Salted Challenge Response Authentication Mechanism (SCRAM) with SHA-256. SCRAM defines how to encode an authentication message to send to the server. It uses the <code>PBKDF2</code> algorithm from the Public-Key Cryptography Standards (PKCS).</p>
<p>Simple Authentication and Security Layer (SASL) then defines a protocol of how to send / receive these authentication messages.</p>
<p>These SASL messages are then wrapped up in HTTP headers and the HTTP request is sent as usual.</p>
<p>If you want the low down on all of the above, here are links to the relevant parts of the specifications:</p>
<ul class="blendChildren">
<li><a href="http://project-haystack.org/doc/Auth">Project HayStack: Authentication</a></li>
<li><a href="https://skyfoundry.com/doc/docSkySpark/Auth">SkySpark Authentication</a></li>
<li><a href="http://tools.ietf.org/html/rfc5802#page-8">RFC5802 - Salted Challenge Response Authentication Mechanism (SCRAM) - Sec. 3 Algorithm Overview</a></li>
<li><a href="http://tools.ietf.org/html/rfc4422#section-3">RFC4422 - Simple Authentication and Security Layer (SASL) - Sec. 3 The Authentication Exchange</a></li>
<li><a href="http://tools.ietf.org/html/rfc2898#section-5.2">RFC2898 - Password-Based Cryptography Specification - Sec. 5.2 PBKDF2</a></li>
<li><a href="https://tools.ietf.org/html/rfc7804">RFC7804: Salted Challenge Response HTTP Authentication Mechanism</a></li>
<li><a href="https://tools.ietf.org/html/rfc7615">RFC7615: HTTP Authentication-Info and Proxy-Authentication-Info Response Header Fields</a></li>
</ul>
<p>The Project Haystack and SkySpark documentation do have a sample client / server conversation (the SASL part), but they make no attempt to explain how they calculated the values and the given messages (the SCRAM part)!</p>
<p>Steps:</p>
<ol class="blendChildren" style="list-style-type: decimal;">
<li><a href="#step1">Hello!</a></li>
<li><a href="#step2">First Message</a></li>
<li><a href="#step3">Second Message</a></li>
<li><a href="#step4">Rest</a></li>
<li><a href="#step5">Fantom Code</a></li>
</ol>
<p>The following example conversation assumes the following values, which are the same as those used in Project Haystack's <a href="http://project-haystack.org/doc/Auth">authentication documentation</a>:</p>
<ul class="blendChildren">
<li>Username: <code>user</code></li>
<li>Password: <code>pencil</code></li>
</ul>
<p>Note that all example code listed here is written in <a href="http://fantom-lang.org/">Fantom</a>.</p>
<h2 id="step1">1. Hello!</h2>
<p>The first request is to initiate the authentication conversation, sending the username we wish to authenticate as.</p>
<p>Note that the URL we authenticate against needs to be a real URL lest we get a 404. It also needs to be one that we're not going to be redirected from. Project specific URLs are fine, but for general purpose usage I find that <code>/ui</code> works well in all scenarios.</p>
<p>Assuming SkySpark is running locally on port 8080 our first HTTP request looks like:</p>
<pre class="syntax microlight notForIe props">// Client Request: Hello
GET /ui HTTP/1.1
Host: localhost:8080
Authorization: HELLO username=dXNlcg
</pre></pre>
<p>The username is just our username <code>user</code> Base64 encoded. But note the lack of trailing <code>=</code> characters that are usually used for padding. That's because the <code>Authorization</code> header uses Base64 URIs, which lack the padding.</p>
<pre class="syntax microlight notForIe props">// Server Response: Hello
HTTP/1.1 401 Unauthorized
WWW-Authenticate: scram handshakeToken=dXNlcg, hash=SHA-256
</pre></pre>
<p>SkySpark replies with the above, which tells us we're to use <code>SHA-256</code> for all our encoding. In general Project Haystack mechanisms this could also be <code>SHA-1</code> or <code>SHA-512</code>.</p>
<p>The <code>handshakeToken</code> is used by the server to keep track of the authentication conversation, similar to a session cookie. So we need to make sure we pass the same value back. (Just ignore the fact it looks like our encoded username - this may change!)</p>
<h2 id="step2">2. First Message</h2>
<p>Send an authentication request to the server.</p>
<p>In the request we name the user we wish to authenticate as, and a nonce. The nonce is random sequence of characters and should be of cryptographic strength.</p>
<pre class="syntax microlight notForIe fantom">clientNonce := ... crypto strength random characters ...
// --> fyko+d2lbbFgONRv9qkxdawL
clientFirstMsg := "n=${userName},r=${clientNonce}"
// --> n=user,r=fyko+d2lbbFgONRv9qkxdawL
</pre></pre>
<p>The first message is then Base64 URI encoded (no trailing <code>=</code> chars) and sent to the server.</p>
<pre class="syntax microlight notForIe props">// Client Request: 1st Message
GET /ui HTTP/1.1
Host: localhost:8080
Authorization: SCRAM handshakeToken=dXNlcg,
data=bj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM
</pre></pre>
<p>SkySpark would then respond with the following response:</p>
<pre class="syntax microlight notForIe props">// Server Response: 1st Message
HTTP/1.1 401 Unauthorized
WWW-Authenticate: scram handshakeToken=dXNlcg, hash=SHA-256,
data=cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0xIbytWZ2s3cXZVT0tVd3VXTElXZzRsLzlTcmFHTUhFRSxzPXJROVpZM01udEJldVAzRTFURFZDNHc9PSxpPTEwMDAw
</pre></pre>
<p>If we Base64 decode the data attribute we get:</p>
<pre class="syntax microlight notForIe ">r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000
</pre></pre>
<p>From which we can derive the following variables:</p>
<pre class="syntax microlight notForIe fantom">serverFirstMsg := "r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000"
serverNonce := "fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE"
serverSalt := "rQ9ZY3MntBeuP3E1TDVC4w=="
serverIterations := 10000
</pre></pre>
<h2 id="step3">3. Second Message</h2>
<p>Send an encoded message that proves we have the password. This is hard part.</p>
<p>First we do the cryptographic stuff. Most languages have classes or libraries for doing this. Fantom uses Java's <a href="https://docs.oracle.com/javase/7/docs/api/javax/crypto/spec/PBEKeySpec.html">javax.crypto.spec.PBEKeySpec</a>. Because we're using the SHA-256 hashing algorithm, we use <code>PBKDF2WithHmacSHA256</code>.</p>
<pre class="syntax microlight notForIe fantom">dkLen := 32 // the size in bytes of a SHA-256 hash
saltedPassword := Buf.pbk("PBKDF2WithHmacSHA256", password, Buf.fromBase64(serverSalt), serverIterations, dkLen)
// --> e3 72 cc 2f 65 c6 ac 00 51 e9 e8 de ef 4b ea a3 f0 72 08 a4 6e b2 9f a3 bf 68 ea 76 9e b8 3e e9
</pre></pre>
<p><code>dkLen</code> is constant, but is dependent on which hashing algorithm we're using.</p>
<pre class="syntax microlight notForIe fantom">// dkLen Values
SHA-1 : 20
SHA-256 : 32
SHA-512 : 64
</pre></pre>
<p>In the next string, <code>clientFinalNoPf</code> (client final no proof), note that <code>biws</code> is a constant and is the just the string <code>"n,,"</code> Base64 encoded. In some literature this is referred to as the <code>GS2 Header</code>.</p>
<p>The string <code>"Client Key"</code> is also constant and is used as a default message to be hashed by the (salted) password.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">clientFinalNoPf := "c=biws,r=${serverNonce}"
// --> "c=biws,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE"
authMessage := "${clientFirstMsg},${serverFirstMsg},${clientFinalNoPf}"
// --> "n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000,c=biws,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE"
clientKey := "Client Key".toBuf.hmac("SHA-256", saltedPassword)
// --> 26 ac fd 4f 40 f9 5e 8e 74 b2 b3 5f 88 cd 8b e4 35 da 89 db 8d ab cc a8 b9 fb de 34 49 f9 93 b8
storedKey := clientKey.toDigest("SHA-256")
// --> b6 2f 2a 50 c9 9e 42 27 46 85 5e 9a 60 fa 3c 71 39 f8 78 9a 70 60 46 19 4d ae 5c e8 cf 48 e5 37
clientSignature := authMessage.toBuf.hmac("SHA-256", storedKey)
// --> 5b 60 ae 4a 75 d8 da 9c 05 3b 85 ef 36 b6 27 df 2c 0a c9 4c f1 65 8f cf 3d 00 e1 1e ee d6 e1 4c
clientProof := xor(clientKey, clientSignature)
// --> 7d cc 53 05 35 21 84 12 71 89 36 b0 be 7b ac 3b 19 d0 40 97 7c ce 43 67 84 fb 3f 2a a7 2f 72 f4
clientFinal := "${clientFinalNoPf},p=${clientProof.toBase64}"
// --> "c=biws,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,p=fcxTBTUhhBJxiTawvnusOxnQQJd8zkNnhPs/KqcvcvQ="
</pre></pre>
<p>The <code>xor()</code> method is an annoying little thing where each byte of each input have to be xored together. It is not a native Fantom function, but instead a method you have to write yourself. See <a href="#step5">Fantom Code</a> for more details.</p>
<p>It is the <code>clientFinal</code> string that we send to the server in the next message. Like last time, we Base64 URI encode it and send it as the <code>data</code> attribute of the <code>Authorization</code> header:</p>
<pre class="syntax microlight notForIe props">// Client Request: 2nd Message
GET /ui HTTP/1.1
Host: localhost:8080
Authorization: SCRAM handshakeToken=dXNlcg,
data=Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMSG8rVmdrN3F2VU9LVXd1V0xJV2c0bC85U3JhR01IRUUscD1mY3hUQlRVaGhCSnhpVGF3dm51c094blFRSmQ4emtObmhQcy9LcWN2Y3ZRPQ
</pre></pre>
<p>SkySpark should then respond with:</p>
<pre class="syntax microlight notForIe props">// Server Response: 2nd Message
HTTP/1.1 200 Auth successful
Authentication-Info: authToken=xxxyyyzzz, hash=SHA-256,
data=dj1UenFKVlc4bk5uZ1o5ZzFiL1lXaU84cy9abEhxQkwyb3AxYmxSN0txZG1FPQ
</pre></pre>
<p>It is the <code>authToken</code> attribute that we're after. We can use in all subsequent HTTP requests to communicate with SkySpark.</p>
<p>But before we do; how do we know we're communicating with the correct server? How do we know our requests aren't be re-routed to some hackers server? Well now is our chance to validate the server and check that <em>it</em> also knows the user's password.</p>
<p>Base64 decoding the <code>data</code> attribute gives us:</p>
<pre class="syntax microlight notForIe ">v=TzqJVW8nNngZ9g1b/YWiO8s/ZlHqBL2op1blR7KqdmE=
</pre></pre>
<p>If the server <em>truly</em> knows the user's password then we should be able to compute the same value for <code>v</code>.</p>
<p>Note the string <code>"Server Key"</code> is constant and is used as a default message to be hashed by the (salted) password.</p>
<pre class="syntax microlight notForIe fantom">serverKey := "Server Key".toBuf.hmac("SHA-256", saltedPassword)
// --> 5a a1 fd ca 03 cb 46 42 45 ba 1b 94 67 a4 2c 9e 61 47 d6 da 9f cc c9 f2 bf 17 bc 4e ab 2c 1a 75
serverSignature := authMessage.toBuf.hmac("SHA-256", serverKey).toBase64
// --> "TzqJVW8nNngZ9g1b/YWiO8s/ZlHqBL2op1blR7KqdmE="
</pre></pre>
<p>Because <code>serverSignature</code> equals the value sent by the server, we can trust it and continue communicating with it.</p>
<h2 id="step4">4. Rest</h2>
<p>Now we have the <code>authToken</code> we can use it in all our requests to the SkySpark REST API.</p>
<pre class="syntax microlight notForIe props">// Client Request: 2nd Message
GET /api/demo/about HTTP/1.1
Host: localhost:8080
Authorization: BEARER authToken=xxxyyyzzz
</pre></pre>
<h2 id="step5">5. Fantom Code</h2>
<p>A complete reference implementation of the SCRAM protocol for retreiving an <code>authToken</code> from SkySpark, written in Fantom, is available in this <a href="https://bitbucket.org/snippets/fantomfactory/AqAKL">BitBucket Snippet</a>.</p>
<p>The sample code may be used like this:</p>
<pre class="syntax microlight notForIe fantom">// get the authToken
authToken := SkySparkAuth().scram(`http://localhost:8080/ui`, "<username>", "<password>")
echo("authToken: ${authToken}")
// call the REST API
zincRes := WebClient(`http://localhost:8080/api/<proj>/about`) {
it.reqHeaders["Authorization"] = "BEARER authToken=${authToken}"
}.getStr
</pre></pre>
<p>Note that the <a href="https://bitbucket.org/brianfrank/haystack-java/overview">haystack-java library</a> also contains a working SCRAM implementation.</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>26 December 2016</em> - Original article.</li>
<li><em>18 May 2017</em> - Made the link to the Fantom implemetation much clearer.</li>
<li><em>26 May 2018</em> - Added notes on <code>"Client Key"</code> and <code>"Server Key"</code> being constant.</li>
</ul>
http://www.alienfactory.co.uk/articles/store-http-sessions-in-mongodb
Store HTTP Sessions in MongoDB
2018-01-22T00:00:00+01:00
2018-01-22T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="/images/articles/2018/store-http-sessions-in-mongodb.mongo.png" alt="MongoDB logo" width="128" height="128"> A quick look at why and how to hold your HTTP Session data in a database.</p>
<p>HTTP Sessions are an integral part of any web application. To quickly get you up and running most web servers, including Fantom's core <a href="http://fantom.org/doc/wisp/index.html">wisp</a> server, store session data in-memory.</p>
<p>Wisp has a pluggable session storage mechanism, meaning the default in-memory storage may be swapped out for a different storage strategy. Benefits of storing session data in a database, over memory, are:</p>
<h3 id="1.Salability">1. Salability</h3>
<p>If your data is held in memory, then the user has to hit that very same server to access their data. While this is fine in dev or for smaller production applications, if you ever need to scale horizontally and add more web servers then things get tricky.</p>
<p>Lets say you now have 5 web servers. You have to make sure each hits the same server every time. Some load balancers can implement <em>sticky sessions</em> and perform other tricks, but it's messy and takes a lot of fiddly configuration. Plus you may still find yourself in a position where one server holds the lion's share of session data, while the other servers idle away!</p>
<p>If your session data is held in a database, then no sticky sessions are required and any server can serve any user.</p>
<h3 id="2.Resilience">2. Resilience</h3>
<p>If you're following any sort of agile / extreme programming methodology (and what's your excuse if you're not!?) then you'll be re-releasing your web application to production often.</p>
<p>While this is a good thing, it also (usually) necessitates an application restart. Which if your session is held in-memory, means all your users automatically get logged out and loose their session data.</p>
<p>But if your session data is held in a database then after an application upgrade, all your logged in users remain logged in and their session data safe! You can re-start your app as many times as you like with minimal disturbance to your users!</p>
<p>This also holds true for failover strategies and other instances of server migration.</p>
<h3 id="3.Visibility">3. Visibility</h3>
<p>You can see how many logged in users are currently using your site simply by querying the database!</p>
<h2 id="how?">How?</h2>
<p>As mentioned, Wisp has a pluggable session storage mechanism. Lets see how this works with <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a>, which is based on Wisp.</p>
<p>Normally you would start a BedSheet application with this:</p>
<pre class="syntax microlight notForIe fantom">BedSheetBuilder("myPodName").startWisp(8069)
</pre></pre>
<p>But we're going to add an option to the builder that tells BedSheet to swap in our MongoDB session store:</p>
<pre class="syntax microlight notForIe fantom">BedSheetBuilder("myPodName") {
it.options["wisp.sessionStore"] = MongoSessionStore#
}.startWisp(8069)
</pre></pre>
<p>This would create an instance of <code>MongoSessionStore</code> using its default constructor; which is fine if the class is self-contained.</p>
<p>But what if we want our <code>MongoSessionStore</code> to be built by the <a href="http://eggbox.fantomfactory.org/pods/afIoc/">IoC</a> in our application to make use of shared services and config?</p>
<p>That gets tricky because the Wisp session store needs to be created <em>before</em> the Wisp server is created and <em>before</em> our IoC application is created. So instead we set a different option:</p>
<pre class="syntax microlight notForIe fantom">BedSheetBuilder("myPodName") {
it.options["wisp.sessionStoreProxy"] = MongoSessionStore#
}.startWisp(8069)
</pre></pre>
<p>This tells BedSheet to create a session store proxy and to register an <code>onRegistryStatup</code> callback. The callback then uses IoC to either lookup <code>MongoSessionStore</code> by type or to autobuild it. All session calls are then routed to our instance!</p>
<p>Our actual <code>MongoSessionStore</code> is then pretty straightforward:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afIoc::Inject
using wisp::WispSessionStore
using afMongo::Collection
using afMongo::Database
using afMongo::Index
using afMorphia::Converters
const class MongoSessionStore : WispSessionStore {
@Inject
private const Converters converters
private const Collection sessions
private const Duration expireSessionsAfter := 60min
private const Str:Obj? emptyMap := Str:Obj?[:].toImmutable
private new make(Database database, |This| f) {
f(this)
sessions = database.collection("Session")
if (!sessions.exists) sessions.create
sessions.index("_timeToLive_")
.ensure(["lastAccessed" : Index.DESC], false, ["expireAfterSeconds":expireSessionsAfter.toSec])
}
override Str:Obj? load(Str id) {
converters.toFantom([Str:Obj?]#, sessions.get(id, false)?.get("data")) ?: emptyMap
}
override Void save(Str id, Str:Obj? map) {
sessions.update(["_id":id], [
"_id" : id,
"data" : converters.toMongo([Str:Obj?]#, map),
"lastAccessed" : DateTime.now(1sec)
], false, true)
}
override Void delete(Str id) {
sessions.delete(["_id":id])
}
}
</pre></pre>
<p>The ctor creates a Mongo Collection for our sessions so we can subsequently create our Index. Note how our Index has an <code>expireAfterSeconds</code> option; this tells MongoDB to automatically delete documents that go out of date, based on the collection's <code>lastAccessed</code> property. Conveniently, this handles the session time out for us!</p>
<p>Note that Mongo's document deletion background task only runs every minute, so if you set the timeout to less than this (for example, to 10 seconds during testing) then your sessions will still survive for 60 seconds or so. But you could always manually check the <code>lastAccessed</code> date if you absolutly need a smaller time-out resolution.</p>
<p>Session data is serialised via Morphia's <a href="http://eggbox.fantomfactory.org/pods/afMorphia/api/Converters">Converters</a> service. This is to overcome inherent problems with <a href="https://stackoverflow.com/questions/12397118/mongodb-dot-in-key-name/30254815#30254815">dotted property keys</a> and to enable a strategy for persisting Fantom data types.</p>
<p>The collection is <code>upserted</code> during a save so the document is automatically inserted if it doesn't exist.</p>
<p><code>load()</code>, <code>save()</code>, and <code>delete()</code> are called by Wisp as and when it feels like it.</p>
<p>And that's it!</p>
<p>Have fun!</p>
<h2 id="dependencies">Dependencies</h2>
<p>This article has been written and tested with the following dependencies:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">depends = [
"sys 1.0.70 - 1.0",
"wisp 1.0.70 - 1.0",
"afBedSheet 1.5.10 - 1.5",
"afIoc 3.0.6 - 3.0",
"afMongo 1.1.6 - 1.1",
"afMorphia 1.2.2 - 1.2",
]
</pre></pre>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>22 January 2018</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/debouncing-and-throttling
Debouncing and Throttling Event Handlers
2017-12-15T00:00:00+01:00
2017-12-15T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="/images/articles/2017/debounce-and-throttle.rubberDuck.png" alt="Rubber Duck" width="150" height="150"> <em>Debouncing and throttling</em> - sounds like something you'd do to a rubber duckie! But they're actually something you do with event handlers!</p>
<p class="lead">They're incredibly useful and this article shows you how to use them in Fantom. Also see the <a href="#visualisation">live demo</a> below!</p>
<p>A problem or two you may have encountered before...</p>
<p>You create a Window resize event handler to re-calculate some complicated layouts when the Window size changes. Only when the user resizes the Window, the OS fires a gazillion resize events every second! No matter how much you optimise the layout code, trying to relayout everything a gazillion times a second maxes the CPU out at 99% and gives the user nothing but a sluggish and laggy experience.</p>
<p><em>Pah!</em></p>
<p>Or you put an text modified event handler on a search box so you can query the server for suggestions based on what the user is typing. It may work great for your boss or Great Aunt who are slow single finger typers, but your speed typing 69 words-per-minute colleague regularly tells you your website is slow and unresponsive! And he doesn't appreciate your suggestion of typing slower so as to not overload the server either!</p>
<p><em>Double pah!</em></p>
<p><img class="img-fluid" src="/images/articles/2017/debounce-and-throttle.google.png" alt="Screenshot of Google Search Suggestions"></p>
<p>Well, <strong>debounce</strong> and <strong>throttle</strong> to the rescue!</p>
<p>The <code>debounce()</code> and <code>throttle()</code> methods (<a href="#code">Fantom code below</a>) allow you to rate-limit your functions in multiple useful ways.</p>
<p>Pass a function in, and get a function out. You then use the returned function in place of the original. The new function may be called as frequently as you want, but the original function is only invoked according the debounce / throttle rules.</p>
<h2 id="visualisation">Visualisation</h2>
<p>Not sure what the difference is between throttling and debouncing? Then see it visually in the real example below!</p>
<p>Hovering over the Fanny will fire an event every 50ms, that is then rate limited in various ways. You can then see which events get processed and when.</p>
<h2 id="vizExample">Example</h2>
<p>Experiment by hovering on and off over Fanny and see when the debounced and throttled functions are actually executed.</p>
<p>Note this is a real example executing real Fantom code!</p>
<h2 id="debouncing">Debouncing</h2>
<p>Debouncing coalesces bursts of function calls into just one. This single function execution may be called at either the end (the default) or at the beginning of the call burst.</p>
<p>This can be useful for <em>resize</em> and <em>mousemove</em> events as your event handler will only get called once the user has stopped resizing, or moving!</p>
<pre class="syntax microlight notForIe fantom">eventHandler := |Event event| { ... }
debouncedFn := Debounce.debounce(200ms, eventHandler)
elem.onEvent("resize", true, debouncedFn)</pre></pre>
<h2 id="throttling">Throttling</h2>
<p>Throttling ensures the original function is only called every XXms. Should the burst of function calls stop, then the original function is invoked one last time to ensure it always has the latest values.</p>
<p>Throttling can be useful for rate limiting the execution Ajax calls to a server, like the search box example.</p>
<pre class="syntax microlight notForIe fantom">eventHandler := |Event event| { ... }
throttledFn := Debounce.throttle(200ms, eventHandler)
elem.onEvent("change", true, throttledFn)</pre></pre>
<p>In terms of rate limiting function calls, <em>throttling</em> is always the first solution that comes to mind. But you should always stop and re-think. Because more often than not, <strong>debouncing</strong> is the solution that works better.</p>
<h2 id="code">Code</h2>
<p>Source code for the <code>debounce()</code> and <code>throttle()</code> methods, complete with documentation, can be found in the following <a href="https://bitbucket.org/snippets/fantomfactory/xenzKp">BitBucket snippet</a>:</p>
<p class="lead"><a href="https://bitbucket.org/snippets/fantomfactory/xenzKp">Debounce and Throttle Code</a></p>
<p>But the inquisitive can view the important bits here. As you can see, the code is quite small!</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom huge">using dom::Win
@Js
mixin Debounce {
** Debounce a function.
static Func debounce(Duration delay, |Obj?, Obj?, Obj?, Obj?| callback, Bool atBegin := false) {
_throttle(delay, callback, null, atBegin)
}
** Throttle a function.
static Func throttle(Duration delay, |Obj?, Obj?, Obj?, Obj?| callback, Bool noTrailing := false) {
_throttle(delay, callback, noTrailing, null)
}
private static Func _throttle(Duration delay, |Obj?, Obj?, Obj?, Obj?| callback, Bool? noTrailing, Bool? atBegin) {
throttling := noTrailing != null
debouncing := atBegin != null
// After wrapper has stopped being called, this timeout ensures that
// 'callback' is executed at the proper times in 'throttle' and 'end'
// debounce modes.
timeoutId := null as Int
lastExec := -1
wrapper := |Obj? p1, Obj? p2, Obj? p3, Obj? p4| {
exec := |->| {
lastExec = Duration.nowTicks
callback(p1, p2, p3, p4)
}
// If `atBegin` is true this is used to clear the flag
// to allow future `callback` executions.
reset := |->| {
timeoutId = null
}
if (debouncing && atBegin && timeoutId == null)
exec()
if (throttling && lastExec == -1)
exec()
elapsed := lastExec > -1 ? Duration.nowTicks - lastExec : 0
if (timeoutId != null)
Win.cur.clearTimeout(timeoutId)
if (throttling)
if (elapsed > delay.ticks)
exec()
else if (!noTrailing)
timeoutId = Win.cur.setTimeout(delay, exec)
if (debouncing)
timeoutId = Win.cur.setTimeout(delay, atBegin ? reset : exec)
}
return wrapper.retype(callback.typeof)
}
}
</pre></pre>
<h2 id="credits">Credits</h2>
<p><code>debounce()</code> and <code>throttle()</code> for Fantom are based on the <a href="http://benalman.com/projects/jquery-throttle-debounce-plugin/">JQuery throttle / debounce Plugin</a> by "Cowboy" Ben Alman.</p>
<p>Visualisation inspired from <a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: a visual explanation</a>.</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>15 December 2017</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/jason-holt-escaped-the-mainframe
Jason Holt Escaped the Mainframe!
2017-12-13T00:00:00+01:00
2017-12-13T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="/images/articles/2017/jason-holt-escaped-the-mainframe.winner.png" alt="Fanny World Champion" width="240" height="233"> Today, Jason Holt became the first person ever to <em>Escape the Mainframe!</em></p>
<p class="lead"><em>Escape the Mainframe</em> is a deceptively simple jump and duck game that tests your reflexes. Rendered in stunning retro 3D vector graphics it follows the exploits of Fanny the Fantom who, like Tron, has been sucked into an evil mainframe computer!</p>
<p class="lead">And now, Alien-Factory has the pleasure and honour of gaining an exclusive interview with the man who has gone down in Fantom gaming history for being the first (and only!) person to complete the game - <strong>Jason Holt!</strong></p>
<hr>
<p><em>Hi Jason, thank you for giving up some of your time today. So, in the great interview tradition, can you start by telling us a bit about yourself?</em></p>
<p class="info">Hi Steve, I'm happy to oblige. I live in Richmond, Virginia, USA, and work for an insurance company. I enjoy doing things outdoors and have an interest in history and cars.</p>
<p><em>Sounds great. So other than retro jump games, what other kind of games do you spend time on?</em></p>
<p class="info">Nothing very fancy in terms of games: on a somewhat regular basis, I play Super Smash Brothers (1999 Nintendo 64 version) with Adam of SkyFoundry. I don't currently have any game consoles, but I grew up with Nintendo and Super Nintendo games.</p>
<p><em>And how did you learn about Escape the Mainframe?</em></p>
<p class="info">I learned about <em>Escape the Mainframe</em> when I noticed the link at the bottom of an email with the teaser, "Can you Escape the Mainframe!?"</p>
<p><em>What do you like about Escape the Mainframe and what compelled you to complete the game?</em></p>
<p class="info">The retro design and the ever-increasing pace are two of the things I like about the game, and the music is pretty cool, too. Practice really helps with this game, so as I got better, I wanted to see how far I could go. I did not really expect to be able to complete the game.</p>
<p><em>But complete it you did! After playing it so much, what would you improve about the game?</em></p>
<p class="info">I can't think of any improvements I would make. It's a very difficult game in the later stages, and any false move ends the game. That makes it more rewarding when you reach a new personal high score.</p>
<p><em>Never mind personal high scores, for as we speak you are THE highest scoring person on the leader board with a whopping score of 2519!</em></p>
<p><em>What tips can you pass on to everyone else to help them to Escape the Mainframe?</em></p>
<p class="info">There's two things that helped me: I use the space bar to jump and the down key to squish. I originally used the up key to jump, but it was awkward to do the squish jump with the buttons right next to each other. The biggest tip to pass on: use the higher practice levels. When I finally started to reach level 8 in the normal game, I would practice at level 10 several times in a row, which made level 8 seem easier.</p>
<p><em>Finally, how does it feel to be the Fanny the Fantom World Champion and the very first person to Escape the Mainframe?</em></p>
<p class="info">Being the first world Fanny champion feels pretty good! When I started playing it a month ago, I couldn't get past level 2. It was exhilarating to escape the mainframe today!</p>
<p class="info">Thanks again, Steve and Emma, for creating this game. It's been a lot of fun and I may try to improve upon my score at some point, but if I don't I'm still satisfied. I'm up for any of Fanny's future endeavours!</p>
<p><em>Thank you Jason. It's good to know we can count on you to tackle any future Fanny the Fantom adventures!</em></p>
<hr>
<p class="lead">So as you can see, it really is possible to escape the Mainframe!</p>
<p class="lead">Jason Holt is currently number 1 on the leader board with a score of 2519. Have you got what it takes to beat Jason?</p>
<p class="lead">Play Escape the Mainframe at <a href="http://escape.fantomfactory.org/">http://escape.fantomfactory.org/</a></p>
<p><img class="img-fluid" src="/images/articles/2017/escape-the-mainframe.screenshot.png" alt="Escape the Mainframe Screenshot"></p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>13 December 2017</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/escape-the-mainframe
Escape the Mainframe
2017-09-05T00:00:00+02:00
2017-09-05T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="/images/articles/2017/escape-the-mainframe.escape.png" alt="Escape the Mainframe"> <em>Fanny the Fantom has been captured by the evil Mainframe. Help Fanny escape dodgy programming by avoiding obstacles and collecting bonus cubes.</em></p>
<p><em>Fanny must jump and duck through 10 frantic levels in this tense retro 3D vector game.</em></p>
<p><em>Can you escape the Mainframe!?</em></p>
<p><em>Escape the Mainframe</em> is a simple jump and duck game to test your reflexes, rendered in stunning retro 3D vector graphics. Written entirely in the Fantom programming language, Escape the Mainframe runs on both the desktop and in a browser!</p>
<p>Play online at <a href="http://escape.fantomfactory.org/">http://escape.fantomfactory.org/</a> or download the <a href="https://bitbucket.org/AlienFactory/afescapethemainframe/downloads">desktop version</a>!</p>
<h2 id="about">About</h2>
<p>Escape the Mainframe is a little program I wrote to raise awareness of the <a href="http://fantom-lang.org/">Fantom programming language</a>.</p>
<p>Fantom is great for advanced server side development, and Fantom rocks when it comes to web applications. But I wanted to show that Fantom is good for the fun things too!</p>
<p><img class="img-fluid" src="/images/articles/2017/escape-the-mainframe.screenshot.png" alt="Escape the Mainframe Screenshot"></p>
<p>Fanny the Fantom is the mascot for Fantom and was named by Andy Frank, one of the creators of Fantom, in a <a href="http://fantom.org/forum/topic/2125#c1">Forum post</a> back in 2013.</p>
<p>Pictures of Fanny can be downloaded as hi-res images, and as web-friendly Scalable Vector Graphics (SVG), on the <a href="http://fantom-lang.org/branding#more-fanny">Fantom-Lang Branding</a> page.</p>
<h2 id="credits">Credits</h2>
<ul class="blendChildren">
<li><strong>Game Design:</strong> Steve & Emma Eynon</li>
<li><strong>Coding:</strong> <a href="http://www.alienfactory.co.uk/aboutMe">SlimerDude</a></li>
<li><strong>Sound Effects:</strong> <a href="https://web.facebook.com/Modulate/">Modulate</a></li>
<li><strong>Music:</strong> <a href="https://www.fiverr.com/zerocakes">ZeroCakes</a></li>
<li><strong>Cartoon Graphics:</strong> <a href="https://www.fiverr.com/ajordaz">Anibal Ordaz</a></li>
</ul>
<h3 id="gameDesign">Game Design</h3>
<p>I've always wanted to do a simple game in Fantom, much like the interactive <a href="https://www.google.com/doodles">Google Doodles</a> or the Chrome <a href="http://www.trex-game.skipser.com/">offline T-Rex game</a>. Only I'm no graphics artist. So I do what I usually do, and relied on coding skills to create visual effects. I went back to some old 3D vector routines and turned them into a jump game.</p>
<p>My wife, Emma, helped enormously to guide the game's design and look & feel.</p>
<p>We wanted to incorporate Fanny the Fantom as the main character so the game had tie-in with the language. The black / blue theme is supposed to be reminiscent of <a href="http://www.fantomfactory.org/">fantomfactory.org</a>, and also looked suspiciously like a retro <a href="https://en.wikipedia.org/wiki/Tron">Tron</a>. We kept the Tron theme and decided to copy the plot, as that would help explain the blocky vector graphics.</p>
<p>The game play in Escape the Mainframe was initially quite speedy with blocks racing across the screen. But you could only have one, maybe two, obstacles on screen at any given time - for there wasn't enough react and jump time to have more. (Fanny has to land before he can jump again!) By accident, one day I slowed everything right down and ended up having lots & lots of blocks on the screen at the same time. It looked way more impressive. And because you could see what was coming up, it was also a lot more engaging and very tricky! So it stayed this way.</p>
<p>We wanted a bonus scheme to break up the monotony of jump, squish, jump, squish, ... We toyed with the idea of different types of bonus cubes: extra lives, invincibility, points, ... and tried floating the cubes over at different speeds, moving them around the screen, etc. Given we expect very few people to actually complete the game, the main goal would be a hi-score, so we settled on a points only bonus. And fixing the bonus to a block seemed to add enough variety.</p>
<p>An absolute must was the ability to persist hi-scores so we could at least compete against each other! A separate project exposes a REST API and persists scores to a MongoDB database.</p>
<p>Fanny was always meant to be played in a browser, but I admit that (as I don't own a tablet or smart phone) touch devices were an after thought. As such Fanny is easier to play with a real keyboard. But hey, it's still playable on a phone, and let's face it - Fanny is not a serious commercial game; it's a cheap'n'cheerful freebie!</p>
<h3 id="coding">Coding</h3>
<p>Escape the Mainframe is coded by me, SlimerDude also known as Steve Eynon. <em>Hello!</em></p>
<p>The game is extremely low level and does not make use of any external libraries, be they OpenGL, graphics, sound, 3D or otherwise. In fact, the only non-core library used is my own <a href="http://eggbox.fantomfactory.org/pods/afIoc/">IoC</a>.</p>
<p>All 3D graphics are calculated from the one <a href="https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions">3D matrix rotation formula</a>:</p>
<pre class="syntax microlight notForIe fantom">y2 := ( y * cos(-ax)) -( z * sin(-ax))
z2 := ( y * sin(-ax)) +( z * cos(-ax))
x2 := ( x * cos(-ay)) +(z2 * sin(-ay))
z3 := -( x * sin(-ay)) +(z2 * cos(-ay))
x3 := (x2 * cos(-az)) -(y2 * sin(-az))
y3 := (x2 * sin(-az)) +(y2 * cos(-az))
</pre></pre>
<p>There is no z-buffering or complex concave hidden surface removal. All objects are convex square-ish shapes with special care taken over their rendering order.</p>
<p>Fanny is cross platform. It runs as a desktop program (in Java) and in an internet browser (in Javascript). All development was done on the desktop because it's easier to run and debug. It then only took some 30 minutes to get it working in Javascript, and that includes writing the web server to serve it up. Thank you Fantom!</p>
<p>During development I was always concerned that it wouldn't run fast enough in a browser, but I actually find Fanny to run faster and smoother in Javascript than what it does on the desktop!</p>
<h3 id="soundEffects">Sound Effects</h3>
<p>All sounds effects and jingles are original and produced by a long standing best mate, Geoff Lee.</p>
<p>Geoff did the music for our <a href="http://www.alienfactory.co.uk/equinox/">Amiga demos</a> when we were in high school - you can sample some of his very early tunes on the Equinox <a href="http://www.alienfactory.co.uk/equinox/slammer">Slammer</a> page.</p>
<p>Unsurprisingly, Geoff followed his passion and became a successful international music producer and DJ, mainly under the industrial rubric of <a href="https://www.facebook.com/Modulate/">Modulate</a> and the more techno inspired <a href="https://www.facebook.com/QLERIK/">QLERIK</a>. See <a href="https://soundcloud.com/modulate">Soundcloud</a> for samples and remixes.</p>
<p>Geoff was naturally my first choice when it came to generating the audio side of Fanny, and after the sounds effects I was really looking forward to him producing the music too. But life happened and he became a single Dad of two wonderful twins who were (are!) far more deserving of his time, forcing me to look elsewhere.</p>
<h3 id="music">Music</h3>
<p>Music is composed by the uber talented chiptune enthusiast, <a href="https://www.fiverr.com/zerocakes">Zerocakes</a> who really gave the title tune a <em>ghostly</em> feel and a cartoon overture.</p>
<p>When it came to a tune for the main game, I was very concerned that any formal composition would soon become annoying and irritating to the player. So I thought about how the player may compose their own tune as the game progresses...</p>
<p>I asked Zerocakes to loop the main bass line from the title tune and create a number of musical fill-ins and melodies to play over the top. These are then played, in time with the bass line, whenever you jump or duck over an obstacle, level up, or perform a tricky manoeuvre. Throw in a quick probability function to ensure more fill-ins are played the higher the level, and you have a coherent tune that never repeats and builds up musical layers the longer a game lasts.</p>
<p>While plugging all this into the game tune, I was careful not to create a blatant cacophony of noise. Some may say I failed but I think it largely works!</p>
<h3 id="cartoonGraphics">Cartoon Graphics</h3>
<p>The cartoon graphics, such as the Fanny mascot himself, the background, and the mainframe computer are drawn by <a href="https://www.fiverr.com/ajordaz">Anibal Ordaz</a>; a talented vector artist in Venezuela.</p>
<p>See the <a href="http://fantom-lang.org/branding">Branding page</a> on <em>fantom-lang.org</em> for examples of other Fanny cartoons, all free to download and use in your own work.</p>
<h2 id="trainingLevels">Training Levels</h2>
<p>The later levels can be quite fun, but not everyone has the skills to easily reach them, so we introduced training levels whereby you can trial any difficultly level.</p>
<p>Training mode keeps the game at the same level until you die, but any score achieved is NOT saved to the Hi-Score board. So no cheating!</p>
<h2 id="cheatCodes">Cheat Codes</h2>
<p>Like any decent retro game, Escape the Mainframe has cheat codes!</p>
<p>Enter any of the following during play to activate:</p>
<ul class="blendChildren">
<li><code>game over</code> - displays end game sequence as if you completed level 10</li>
<li><code>invisible</code> - permanently activates <em>ghost</em> mode making you impervious to obstacles</li>
<li><code>god mode</code> - like <em>invisible</em> only with a different colour scheme</li>
<li><code>rainbow</code> - multi-coloured blocks</li>
</ul>
<p>Note that, similar to training mode, any score achieved while cheating is NOT saved to the Hi-Score board.</p>
<h2 id="links">Links</h2>
<ul class="blendChildren">
<li><a href="http://escape.fantomfactory.org/">Play "Escape the Mainframe" Online</a></li>
<li><a href="http://eggbox.fantomfactory.org/pods/afEscapeTheMainframe/">Download Desktop verstion</a></li>
<li><a href="http://escape.fantomfactory.org/">Eggbox pod repository</a></li>
</ul>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>5 September 2017</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/flying-quadcopters
Flying Quadcopters with Fantom
2017-07-05T00:00:00+02:00
2017-07-12T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead">In this article we're going to write a program in the Fantom programming language to control the Parrot AR Drone quadcopter.</p>
<p><img class="img-fluid" src="/images/articles/2017/flying-quadcopters.fanny.png" alt="Fanny riding a Quadcopter"></p>
<p>In particular, the program will aim to:</p>
<ul class="blendChildren">
<li>control the drone via keyboard input</li>
<li>print real-time telemetry data from the drone</li>
<li>display a real-time video feed from the drone's camera</li>
</ul>
<p>This article assumes a familiarity with programming languages and that Fantom is already installed on your system.</p>
<p>The Parrot A.R. Drone is a popular low cost quadcopter. It is often the drone of choice for quadcopter enthusiasts due to it's open source client SDK written in C.</p>
<p>On the Fantom side, the program will use the Parrot Drone SDK by Alien-Factory. It is a pure Fantom implementation of the Parrot SDK that lets you pilot Parrot quadcopters remotely.</p>
<ol class="blendChildren" style="list-style-type: decimal;">
<li><a href="#control">Controlling the Parrot AR Drone</a></li>
<li><a href="#project">Create Fantom Project</a></li>
<li><a href="#telemetry">Print Telemetry Data</a></li>
<li><a href="#keyboard">Keyboard Control</a></li>
<li><a href="#video">Video Stream</a></li>
<li><a href="#together">Putting It All Together</a></li>
<li><a href="#finale">Finale</a></li>
<li><a href="#references">References</a></li>
</ol>
<p>.</p>
<h2 id="control">1. Controlling the Parrot AR Drone</h2>
<p>The Parrot AR Drone has an on-board microprocessor that runs Linux Busy Box. It uses this to read sensors and send output to its 4 motors. It also contains Wifi hardware. When you turn the drone on, it sets itself up as a Wifi hot spot, to which your computer connects.</p>
<p>The drone and your computer then use standard TCP and UDP protocols to send and receive data. In particular, your computer sends configuration and movement commands, and the drone sends back video feeds and navigation data.</p>
<p>On our computer we're going to be running a Fantom program that uses the Parrot Drone SDK library to send flying commands to the drone. To make use of video streaming we need to use the popular FFMEG utility to convert raw video data from the drone into usable images. To use FFMEG, just ensure the executable is on your PATH or in the same directory as from where you start Fantom.</p>
<h2 id="project">2. Create Fantom Project</h2>
<p>The first thing is to create a simple project that opens a window to display text and receive keyboard input.</p>
<p>Fantom projects are compiled into a <code>.pod</code> file, much like how Java projects are compiled into <code>.jar</code> files. Only in Fantom you are encouraged to make <code>.pod</code> files self contained so they often contain documentation, source code, and any related resources.</p>
<p>Every Fantom project has a <code>build.fan</code> file. This is a Fantom script that's responsible for creating the <code>.pod</code> file. Conveniently, Fantom is bundled with a core library called <code>build</code> that contains a lot of utility classes and methods that do most of the pod building work for you. In particular, if you subclass the <code>BuildPod</code> class then all you need to do is configure it in the constructor!</p>
<p>Here's the <code>build.fan</code> we're gonna use for our JaxDrone project:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using build
class Build : BuildPod {
new make() {
podName = "jaxDrone"
summary = "Controller for the Parrot AR Drone"
version = Version("1.0")
depends = [
"sys 1.0.69 - 1.0",
"gfx 1.0.69 - 1.0",
"fwt 1.0.69 - 1.0",
"afParrotSdk2 0.0.8 - 0.1",
]
srcDirs = [`fan/`]
resDirs = [,]
docApi = true
docSrc = true
}
}
</pre></pre>
<p>Our demo project will be compiled into a file called <code>jaxDrone.pod</code> and has a number of dependencies:</p>
<ul class="blendChildren">
<li><code>sys</code> - the core Fantom library</li>
<li><code>gfx</code> - contains useful constructs like <code>Color</code> & <code>Font</code></li>
<li><code>fwt</code> - Fantom Windowing Toolkit for creating Window applications; based on eclipse SWT.</li>
<li><code>afParrotSdk2</code> - a library from Alien-Factory (as denoted by the <code>af</code> prefix) that controls the Parrot AR Drone.</li>
</ul>
<p>The pods <code>sys</code>, <code>gfx</code>, and <code>fwt</code> are core pods that come pre-bundled so any Fantom installation should already include them. <code>afParrotSdk2</code> however, is a third party pod that we need to download and install. Running the following Fantom command should do just that.</p>
<pre class="syntax microlight notForIe ">fanr install -r http://eggbox.fantomfactory.org/fanr/ afParrotSdk2</pre></pre>
<p>As per the <code>srcDirs</code> we will put our source code in the <code>fan/</code> directory, starting with a <code>Main</code> class:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using fwt::Desktop
using fwt::Label
using fwt::Window
using gfx::Color
using gfx::Font
using gfx::Size
class Main {
Void main() {
Window {
it.title = "Fantom AR Drone Controller"
it.size = Size(440, 340)
label := Label {
it.font = Desktop.sysFontMonospace.toSize(10)
it.bg = Color(0x202020) // dark grey
it.fg = Color(0x31E722) // bright green
}
it.add(screen)
it.onOpen.add |->| {
label.text = "Let's fly!"
}
it.onClose.add |->| {
label.text = "Goodbye!"
}
}.open
}
}
</pre></pre>
<p>It creates a window with a <code>Label</code> and prints some text when it is opened. We decorate the label so it looks like a console window, complete with green text on a black background in a monospace font.</p>
<p>To build our pod, run the <code>build.fan</code> Fantom script:</p>
<pre class="syntax microlight notForIe ">> fan build.fan
compile [jaxDrone]
Compile [jaxDrone]
FindSourceFiles [1 files]
WritePod [file:/C:/Apps/fantom-1.0.69/lib/fan/jaxDrone.pod]
BUILD SUCCESS [127ms]!
</pre></pre>
<p>See <a href="http://fantom.org/doc/docTools/Build">Build</a> in the Fantom Tools documentation for more details on building pods.</p>
<p>Then to run our project, run the newly built pod:</p>
<pre class="syntax microlight notForIe ">> fan jaxDrone
</pre></pre>
<p><img class="img-fluid" src="/images/articles/2017/flying-quadcopters.basicApp.png" alt="Screenshot of basic FWT Window"></p>
<p>See <a href="http://fantom.org/doc/docTools/Build">Running Pods</a> in the Fantom Tools documentation for more details on running pods.</p>
<h2 id="telemetry">3. Print Telemetry Data</h2>
<p>Next comes the exciting bit - connecting to the drone itself! For this bit we'll introduce the ParrotSDK library for Fantom. The library is centred around the <a href="http://eggbox.fantomfactory.org/pods/afParrotSdk2/api/Drone">Drone</a> class, so we'll add it as a <code>using</code> statement and instantiate an instance.</p>
<p>We'll update the <code>onOpen()</code> and <code>onClose()</code> events to connect and disconnect to/from the drone respectively.</p>
<p>After we've connected to the drone, we'll call <code>clearEmergency()</code> to ensure the drone is in a normal flying state. We'll also call <code>flatTrim()</code> so the drone can calibrate where horizontal is, needed for a stable and wobble free flight!</p>
<p>We'll also take this opportunity to set up a control loop which will be executed every 30 milliseconds or so. In this loop we'll be updating the screen with fresh telemetry data, sending movement commands, and processing video data.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afParrotSdk2::Drone
using fwt::Desktop
using fwt::Label
using fwt::Window
using gfx::Color
using gfx::Font
using gfx::Size
class Main {
Drone? drone
Label? label
Screen? screen
Void main() {
label = Label {
it.font = Desktop.sysFontMonospace.toSize(10)
it.bg = Color(0x202020) // dark grey
it.fg = Color(0x31E722) // bright green
}
Window {
it.title = "Fantom AR Drone Controller"
it.size = Size(440, 340)
it.add(label)
it.onOpen.add |->| { connect() }
it.onClose.add |->| { disconnect() }
}.open
}
Void connect() {
drone = Drone()
screen = Screen(drone, label)
drone.connect
drone.clearEmergency
drone.flatTrim
controlLoop()
}
Void disconnect() {
drone.disconnect()
}
Void controlLoop() {
screen.printDroneInfo()
Desktop.callLater(30ms) |->| { controlLoop() }
}
}
</pre></pre>
<p>In the first instance we'll just use the control loop to update the screen with telemetry data. In standard demo mode, the drone will send telemetry data every 60 milliseconds (about 15 times a seconds) so our 30 millisecond loop will be ample.</p>
<p>The <a href="http://eggbox.fantomfactory.org/pods/afParrotSdk2/api/Drone#navData">Drone.navData</a> field always contains the latest data from the drone, so our <code>Screen</code> class will just print data from that.</p>
<p>If we wanted to write an event driven display then we could utilise the <code>Drone.onNavData</code> event handler, but our loop keeps things simple.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afParrotSdk2::Drone
using fwt::Key
using fwt::Label
class Screen {
private const Int screenWidth := 52 // in characters
private const Int screenHeight := 20 // in characters
private Label label
private Drone drone
new make(Drone drone, Label label) {
this.drone = drone
this.label = label
}
Void printDroneInfo() {
buf := StrBuf(screenWidth * screenHeight)
buf.add(logo("Connected to ${drone.config.droneName}"))
navData := drone.navData
data := navData.demoData
flags := navData.flags
buf.addChar('\n')
buf.addChar('\n')
buf.add("Flight Status: ${data.flightState.name.toDisplayName}\n")
buf.add("Battery Level: ${data.batteryPercentage}%\n")
buf.add("Altitude : " + data.altitude.toLocale("0.00") + " m\n")
buf.add("Orientation : X:${num(data.phi )} Y:${num(data.theta )} Z:${num(data.psi )}\n")
buf.add("Velocity : X:${num(data.velocityX)} Y:${num(data.velocityY)} Z:${num(data.velocityZ)}\n")
buf.addChar('\n')
// show some common flags / problems
if (flags.flying) buf.add(centre("--- Flying ---"))
if (flags.emergencyLanding) buf.add(centre("*** EMERGENCY ***"))
if (flags.batteryTooLow) buf.add(centre("*** BATTERY LOW ***"))
if (flags.anglesOutOufRange)buf.add(centre("*** TOO MUCH TILT ***"))
label.text = alignTop(buf.toStr)
}
private Str logo(Str text) {
padding := " " * (screenWidth - 10 - text.size)
return
" _____
/X | X\\ A.R. Drone Controller
|__\\#/__| by Alien-Factory
| /#\\ |
\\X_|_X/ $padding $text"
}
private Str num(Float num) {
num.toLocale("0.00").justr(7)
}
private Str centre(Str txt) {
" " * ((screenWidth - txt.size) / 2) + txt
}
private Str alignTop(Str txt) {
txt + ("\n" * (screenHeight - txt.numNewlines))
}
}
</pre></pre>
<p>The <code>printDroneInfo()</code> method prints drone data out to a string buffer and sets it as the label text.</p>
<p>Some common emergency flags are printed at the bottom that alert you to problems, should you not be aware that that drone has just crash landed!</p>
<p>The <code>alignTop()</code> method just makes sure the text is aligned at the top of the label by padding it out with extra new lines. The other methods perform benign formatting.</p>
<p>Note that the <code>Velocity</code> and <code>Orientation</code> data updates even when the drone is not flying. So now is a good time to try out our program!</p>
<p>Before you build and run the pod again, turn the drone on (connect up its battery) and wait for it to power up. It should then start broadcasting an open WiFi hotspot calling something like <code>ardrone2_v2.4.8</code>. Connect to it as you would any other, then build and run the pod:</p>
<p><img class="img-fluid" src="/images/articles/2017/flying-quadcopters.telemetry.png" alt="Screenshot of telemetry data"></p>
<p>Now pick up your AR Drone and play with it, pretending it's a F16 fighter jet or a X-Wing starfighter, and you should see the telemetry data update on the screen!</p>
<h2 id="keyboard">4. Keyboard Control</h2>
<p>To control the drone via keyboard we'll create a <code>Controller</code> class. It will hook into the Window's <code>keyUp</code> and <code>keyDown</code> events to maintain a list of keys that are currently pressed <em>down</em>. We'll monitor the <code>WASD</code> and cursor keys to perform the following:</p>
<ul class="blendChildren">
<li><code>W</code> & <code>S</code> - tilt drone forward and backward</li>
<li><code>A</code> & <code>D</code> - tilt drone left and right</li>
<li><code>Up</code> & <code>Down</code> - move drone up and down</li>
<li><code>Left</code> & <code>Right</code> - spin drone clockwise and anti-clockwise</li>
</ul>
<p>We'll also use the following keys for <em>special</em> commands:</p>
<ul class="blendChildren">
<li><code>Enter</code> - take off and land</li>
<li><code>Esc</code> - emergency landing!</li>
</ul>
<p>Because we're using the <code>Enter</code> key for both take off and landing, we need to first check what the drone is doing before we issue a command. Note that asking the drone to take off puts it in a stable hover state where it hovers at a height of about 1 meter above the ground. It can sometimes take up to 5 seconds for this stable hover to be achieved - at which point the drone sets the <code>flying</code> flag.</p>
<p>The emergency landing key is our back up should anything go wrong. Hitting the <code>Esc</code> keys sets the <code>User Emergency</code> flag which cuts power to the drones engines, ensuring it falls (ungracefully) out of the sky - which is sometimes better than watching it go sailing over the neighbours hedge!</p>
<p>When performing a special command, we'll clear the list of depressed keys so we don't confuse the drone by trying to make it so several things at once!</p>
<p>The <code>Main</code> class needs to be updated to create an instance of a new <code>Controller</code> class and call it during the control loop. And this is the <code>Controller</code> class:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afParrotSdk2::Drone
using afParrotSdk2::FlightState
using fwt::Event
using fwt::Key
using fwt::Window
class Controller {
private Drone drone
private Key[] keys := Key[,]
new make(Drone drone, Window window) {
this.drone = drone
window.onKeyUp.add |Event e| { keys.add(e.key) }
window.onKeyDown.add |Event e| { keys.remove(e.key) }
}
Void controlDrone() {
if (keys.contains(Key.esc)) {
keys.clear()
drone.setUserEmergency()
}
if (keys.contains(Key.enter)) {
keys.clear()
if (drone.flightState == FlightState.def || drone.flightState == FlightState.landed) {
drone.clearEmergency
drone.takeOff(false)
} else {
drone.land(false)
}
}
roll := 0f
pitch := 0f
vert := 0f
yaw := 0f
if (keys.contains(Key.a)) roll = -1f
if (keys.contains(Key.d)) roll = 1f
if (keys.contains(Key.w)) pitch = -1f
if (keys.contains(Key.s)) pitch = 1f
if (keys.contains(Key.down)) vert = -1f
if (keys.contains(Key.up)) vert = 1f
if (keys.contains(Key.left)) yaw = -1f
if (keys.contains(Key.right)) yaw = 1f
drone.move(roll, pitch, vert, yaw)
}
}
</pre></pre>
<h2 id="video">5. Video Stream</h2>
<p>The cool part of controlling a drone is being able to see what it sees, so let's create a <code>DroneCam</code> class!</p>
<p>For this, we'll use the <a href="http://eggbox.fantomfactory.org/pods/afParrotSdk2/api/VideoStreamer">VideoStreamer</a> class, which requires the FFMPEG utility to be on the <code>PATH</code>. Once we've configured the video and attached it to the live stream on the front camera, then the drone starts to send out raw video data (encoded H.264 frames). The <code>VideoStreamer</code> intercepts these video frames and uses FFMPEG to convert them to PNG images.</p>
<p>To display PNG images, we subclass the FWT <code>Canvas</code> class. The <code>Canvas</code> class, much like a HTML 5 canvas object, creates its content by painting. The only thing our <code>CamCanvas</code> class paints is the PNG image. Only we need to make sure we dispose of any previous images, otherwise it creates huge memory leaks!</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afParrotSdk2::Drone
using afParrotSdk2::VideoCamera
using afParrotSdk2::VideoResolution
using afParrotSdk2::VideoStreamer
using fwt::Canvas
using fwt::Desktop
using fwt::Window
using gfx::Graphics
using gfx::Image
using gfx::Size
class DroneCam {
Drone drone
Window window
VideoStreamer streamer := VideoStreamer.toPngImages
CamCanvas canvas := CamCanvas()
new make(Drone drone, Window window) {
this.drone = drone
this.window = window
}
Void open() {
drone.config.session("jaxDemo").with {
videoCamera = VideoCamera.horizontal
videoResolution = VideoResolution._360p
}
streamer.attachToLiveStream(drone)
// open a new window, attaching it as a child of the existing window
// open() blocks until window is closed, so call it in its own thread
Desktop.callAsync |->| {
Window(window) {
it.title = "AR Drone Cam"
it.size = Size(640, 360)
it.add(canvas)
}.open
}
}
Void updateVideoStream() {
canvas.onPngImage(streamer.pngImage)
}
}
class CamCanvas : Canvas {
Image? pngImage
Void onPngImage(Buf? pngBuf) {
if (pngBuf == null) return
// you get a MASSIVE memory leak if you don't call this!
pngImage?.dispose
// note this creates is an in-memory file, not a real file
pngImage = Image(pngBuf.toFile(`droneCam.png`))
this.repaint
}
override Void onPaint(Graphics g) {
if (pngImage != null)
g.drawImage(pngImage, 0, 0)
}
}
</pre></pre>
<p>And now you can view a live video feed from the Drone!</p>
<p><img class="img-fluid" src="/images/articles/2017/flying-quadcopters.droneCam.jpg" alt="Screenshot of Drone Cam"></p>
<h2 id="together">6. Putting It All Together</h2>
<p>The final project should look like this:</p>
<pre class="syntax microlight notForIe ">jaxDrone/
|-- fan/
| |-- Controller.fan
| |-- DroneCam.fan
| |-- Main.fan
| `-- Screen.fan
`-- build.fan
</pre></pre>
<p>For the sake of completion, here is final <code>Main</code> class that shows how to setup and call <code>Screen</code>, <code>Controller</code>, and <code>DroneCam</code>:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afParrotSdk2::Drone
using fwt::Desktop
using fwt::Label
using fwt::Window
using gfx::Color
using gfx::Font
using gfx::Size
using gfx::Image
class Main {
Drone? drone
Label? label
Screen? screen
Controller? controller
DroneCam? droneCam
Void main() {
label = Label {
it.font = Desktop.sysFontMonospace.toSize(10)
it.bg = Color(0x202020) // dark grey
it.fg = Color(0x31E722) // bright green
}
Window {
it.title = "Fantom AR Drone Controller"
it.size = Size(440, 340)
it.add(label)
it.onOpen.add |->| { connect() }
it.onClose.add |->| { disconnect() }
}.open
}
Void connect() {
drone = Drone()
screen = Screen(drone, label)
controller = Controller(drone, label.window)
droneCam = DroneCam(drone, label.window)
drone.connect
drone.clearEmergency
drone.flatTrim
droneCam.open
controlLoop()
}
Void disconnect() {
drone.disconnect()
}
Void controlLoop() {
screen.printDroneInfo()
controller.controlDrone()
droneCam.updateVideoStream()
Desktop.callLater(30ms) |->| { controlLoop() }
}
}
</pre></pre>
<h2 id="finale">7. Finale</h2>
<p>This article has looked at the Fantom Programming Language and the Parrot SDK 2 for the AR Drone and we've covered quite a lot:</p>
<ul class="blendChildren">
<li>Set up simple Fantom project, complete with build script</li>
<li>Created a basic window application</li>
<li>Connected to the AR Drone via WiFi</li>
<li>Updated real time telemetry data to the window</li>
<li>Added keyboard controls for flying the drone</li>
<li>Shown real-time video feed from the Drone's camera</li>
</ul>
<p>As to what happens next is up to you!</p>
<p>But me? Well, I'll be assigning some of the built-in stunt manoeuvres to function keys! Hmm, lets see... I think F1 for a backward flip, F2 for a psi dance, F3 for a wave...</p>
<p>Have fun!</p>
<h2 id="references">References</h2>
<p>The following versions were used in the making of this article:</p>
<ul class="blendChildren">
<li><a href="https://www.parrot.com/uk/drones/parrot-ARDrone-20-elite-%25C3%25A9dition">Parrot AR Drone 2.0</a></li>
<li><a href="http://fantom-lang.org/download">Fantom 1.0.69</a></li>
<li><a href="http://eggbox.fantomfactory.org/pods/afParrotSdk2/">Parrot SDK 2 0.0.6</a></li>
<li><a href="https://ffmpeg.org/">FFMPEG 3.3.0</a></li>
</ul>
<p>Other resources:</p>
<ul class="blendChildren">
<li><a href="http://fantom-lang.org/">Fantom Programming Language</a></li>
<li><a href="https://www.parrot.com/us/drones/parrot-ardrone-20-elite-%25C3%25A9dition">Parrot AR Drone</a></li>
<li><a href="http://forum.developer.parrot.com/c/drone-sdk/ardrone">Parrot AR Drone SDK Forum</a></li>
<li><a href="/articles/autonomous-flips-with-the-parrot-ar-drone">Autonomous Flips with the Parrot AR Drone</a></li>
</ul>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>12 July 2017</em> - Article re-mastered and re-published on Alien-Factory.</li>
<li><em>7 July 2017</em> - Article translated to German:
<ul class="blendChildren">
<li><a href="https://jaxenter.de/quadrocopter-programmieren-fantom-59278">Grundkurs Drohnensteuerung: So lernen Quadrocopter mit Fantom fliegen</a></li>
</ul>
</li>
<li><em>5 July 2017</em> - Original article published on JAXenter:
<ul class="blendChildren">
<li><a href="https://jaxenter.com/flying-quadcopters-fantom-135398.html">Drone controlling 101: Flying Quadcopters with Fantom</a></li>
</ul>
</li>
</ul>
http://www.alienfactory.co.uk/articles/autonomous-flips-with-the-parrot-ar-drone
Autonomous Flips with the Parrot AR Drone
2017-05-19T00:00:00+02:00
2017-05-19T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p><div class="noPrint"><iframe width="800" height="450" src="https://www.youtube.com/embed/nL8LvGWCWhE?showinfo=0" frameborder="0" allowfullscreen style="border: 4px solid #20ee23;"></iframe></div><div class="text-xs-center" style="width: 800px"><a href="https://www.youtube.com/watch?v=nL8LvGWCWhE" class="lead">Watch Video on YouTube </a></div></p>
<p class="lead">A look at the Parrot AR Drone quadcopter and how to make it fly autonomously and perform tricks - all using the Fantom programming language!</p>
<h2 id="parrotARDrone">Parrot AR Drone</h2>
<p>If you've not used an AR Drone before, I'll explain the control mechanism.</p>
<p>The drone itself has an on-board microprocessor and runs <a href="https://www.busybox.net/about.html">Linux Busy Box</a>. It uses this to read its various sensors and send output to its 4 motors. It also contains Wifi hardware.</p>
<p><img class="img-fluid" src="/images/articles/2017/autonomous-flips.comms.png" alt="AR Drone transmitting Wifi"></p>
<p>When you turn the drone on it sets itself up as a Wifi hot spot, to which your computer connects.</p>
<p>The drone, and your computer, use standard TCP and UDP protocols to send and receive data. In particular, your computer sends configuration and movement commands and the drone sends back video feeds and navigation data.</p>
<h2 id="parrotSDK">Parrot SDK</h2>
<p>On our computer we're going to be running a Fantom program, and we're going to use the <a href="http://eggbox.fantomfactory.org/pods/afParrotSdk2/">Parrot Drone SDK</a> library to send flying commands to the drone.</p>
<p>The drone SDK library is produced by Alien-Factory and is one of the many libraries freely available on the <a href="http://eggbox.fantomfactory.org/pods/">Eggbox pod repository</a>.</p>
<p>A quick glance at the API shows that most functionality is centred on the <code>Drone</code> class. While I can't deny there are lots of slots on the <code>Drone</code> class note there are <em>9 methods</em> of moving the drone and <em>9 event handlers</em>. So once you take these into account the <code>Drone</code> class becomes quite manageable.</p>
<p>Also note the <code>VideoStreamer</code> class that we'll be using to download video from the drone. <code>VideoStreamer</code> requires the popular <a href="https://ffmpeg.org/">FFMEG</a> utility to convert the raw data from the drone to an easily playable MP4 file.</p>
<p>FFMEG is also freely available. Just head to the website, click download, and choose one of the pre-made builds. Once extacted you'll notice it's just one big executable file (~40Mb).</p>
<p>To install Parrot Drone SDK 2 with <a href="http://eggbox.fantomfactory.org/pods/afFpm/">Fantom Pod Manager</a>, cut'n'paste the following into a cmd prompt, terminal or shell:</p>
<pre class="syntax microlight notForIe ">fpm install afParrotSdk2</pre></pre>
<p>Or to install Parrot Drone SDK 2 with the Fantom Repository Manager ( <a href="http://fantom.org/doc/docFanr/Tool.html#install">fanr</a> ), cut'n'paste the following:</p>
<pre class="syntax microlight notForIe ">fanr install -r http://eggbox.fantomfactory.org/fanr/ afParrotSdk2</pre></pre>
<h2 id="parrotProgram">Parrot Program</h2>
<p>As for the drone program itself we're just going to use the <em>Simple Example</em> bundled with the SDK documentation. So just cut'n'paste that into a new file called <code>SimpleExample.fan</code>.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afParrotSdk2
using concurrent::Actor
class SimpleExample {
Void main() {
drone := Drone().connect
drone.clearEmergency()
drone.flatTrim()
// set some outdoor configuration
drone.config.useOutdoorProfile = true
drone.config.useOutdoorShell = true
// record the action to an MP4 file
drone.config.session("Simple Example")
drone.config.session.videoCamera = VideoCamera.horizontal
drone.config.session.videoResolution = VideoResolution._720p
VideoStreamer.toMp4File(`simpleExample.mp4`).attachToLiveStream(drone)
// let's fly!!!
drone.takeOff
drone.animateFlight(FlightAnimation.flipBackward)
Actor.sleep(2sec)
drone.land
drone.disconnect
}
}
</pre></pre>
<p>The first part creates a new instance of the Drone class and initialises it for take off. The <code>clearEmergency()</code> puts the drone into a normal state, and <code>flatTrim()</code> tells it to calibrate while it's lying flat on the ground.</p>
<pre class="syntax microlight notForIe fantom">drone := Drone().connect
drone.clearEmergency()
drone.flatTrim()
</pre></pre>
<p>Then we give the drone some information about its environment. I usually use the indoor shell, because it has some really <strong>BIG Bumpers</strong> and I crash my drone... <strong>a lot!</strong></p>
<pre class="syntax microlight notForIe fantom">// set some outdoor configuration
drone.config.useOutdoorProfile = true
drone.config.useOutdoorShell = false
</pre></pre>
<p>The next bit activates the drone's front camera and record the video to an MP4 file.</p>
<pre class="syntax microlight notForIe fantom">// record the action to an MP4 file
drone.config.session("Simple Example")
drone.config.session.videoCamera = VideoCamera.horizontal
drone.config.session.videoResolution = VideoResolution._720p
VideoStreamer.toMp4File(`simpleExample.mp4`).attachToLiveStream(drone)
</pre></pre>
<p>Then we have the actual flight plan. We're going to take off, do a backwards flip, hover for a bit, and then land.</p>
<pre class="syntax microlight notForIe fantom">// let's fly!!!
drone.takeOff
drone.animateFlight(FlightAnimation.flipBackward)
Actor.sleep(2sec)
drone.land
</pre></pre>
<p>Easy!</p>
<p>But note that for accurate and precision flying you need to have kept good care of your drone. This includes cleaning and balancing your rotors, and replacing any key mechanical parts with high performance variants. There are loads of videos on YouTube showing you how to do this, for example:</p>
<ul class="blendChildren">
<li><a href="https://www.youtube.com/watch?v=-xQCgCl2Rg4">AR Drone 2.0 Bearings Upgrade</a></li>
<li><a href="https://www.youtube.com/watch?v=MeC7hPZUOP4">AR.Drone 2.0 Fix for Warped Propellors</a></li>
<li><a href="https://www.youtube.com/watch?v=g6X9ceaelXQ">AR Drone 2.0 How to Balance Propellers</a></li>
</ul>
<p>Now, I crash my drone quite a lot, so it's never in the best condition! As you'll see in the video, this makes my drone rather, um, unstable!</p>
<p>Anyway, without further a do, lets heat up the drone, connect to its Wifi, and let's fly!</p>
<p><img class="img-fluid" src="/images/articles/2017/autonomous-flips.inAction.jpg" alt="AR Drone performing a flip"></p>
<p>That was the Parrot AR Drone, performing an autonomous flip, controlled by Fantom!</p>
<p>Have Fun!</p>
<p><em>Note that 2 drones died in the making of this video.</em> :(</p>
<h2 id="versions">Versions</h2>
<p>The following versions were used in the making of this article:</p>
<ul class="blendChildren">
<li><a href="https://www.parrot.com/uk/drones/parrot-ARDrone-20-elite-%25C3%25A9dition">Parrot AR Drone 2.0</a></li>
<li><a href="http://fantom-lang.org/">Fantom 1.0.69</a></li>
<li><a href="http://eggbox.fantomfactory.org/pods/afParrotSdk2/">Parrot SDK 2 0.0.6</a></li>
<li><a href="https://ffmpeg.org/">FFMPEG 3.3.0</a></li>
</ul>
<h2 id="alsoSee">Also See</h2>
<ul class="blendChildren">
<li><a href="/articles/flying-quadcopters">Flying Quadcopters with Fantom</a></li>
</ul>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>19 May 2017</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/skyspark-bedsheet-extension
SkySpark v3.0 BedSheet Extension
2017-04-29T00:00:00+02:00
2017-04-29T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead">How to create a BedSheet Extension for SkySpark 3.0</p>
<p><img class="img-fluid" src="/images/articles/2017/skyspark-bedsheet-extension.skySpark.png" alt="SkySpark Logo"></p>
<p><a href="http://eggbox.fantomfactory.org/pods/afBedSheet">BedSheet</a> is a great Fantom framework for web applications. It provides not only an IoC container but also a rich Middleware mechanism for routing and manipulating HTTP content. But given I wrote <em>BedSheet</em>, I guess I'm also quite biased!</p>
<p>Anyway, BedSheet is built on top of Fantom's <a href="http://fantom.org/doc/wisp/index">Wisp</a>, one of the cornerstones of <a href="https://skyfoundry.com/skyspark/">SkySpark</a>. So just for fun I thought I'd build a <a href="https://skyfoundry.com/doc/docSkySpark/Exts#extClass">SkySpark web extension</a> that integrates with a BedSheet application - and it turned out to be quite easy.</p>
<p>Following is a SkySpark web <a href="https://skyfoundry.com/doc/skyarcd/Ext">Ext</a> that delegates all web requests to BedSheet.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using skyarcd::Ext
using skyarcd::ExtMeta
using concurrent::AtomicRef
using afIoc
using afBedSheet::MiddlewarePipeline
using web::Weblet
@ExtMeta { name = "bedsheet" }
const class BedSheetExt : Ext, Weblet {
private const AtomicRef registryRef := AtomicRef(null)
private Registry registry {
get { registryRef.val }
set { registryRef.val = it }
}
private const AtomicRef pipelineRef := AtomicRef(null)
private MiddlewarePipeline pipeline {
get { pipelineRef.val }
set { pipelineRef.val = it }
}
override Void onStart() {
this.registry = RegistryBuilder().addModulesFromPod("afBedSheet").addModule(AppModule#).build
this.pipeline = registry.activeScope.serviceById(MiddlewarePipeline#.qname)
}
override Void onService() {
registry.activeScope.createChild("request") {
// this is the actual call to BedSheet!
pipeline.service
}
}
override Void onStop() {
registry.shutdown
}
}
</pre></pre>
<p>The actual BedSheet application I used is just the <em>Quick Start example</em> from the <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/doc/">BedSheet documentation</a>.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using afIoc
using afBedSheet
const class AppModule {
@Contribute { serviceType=Routes# }
Void contributeRoutes(Configuration conf) {
conf.add(Route(`/`, Text.fromHtml("<html><body>Welcome to BedSheet!</body></html>")))
conf.add(Route(`/hello/**`, HelloPage#hello))
}
}
class HelloPage {
Text hello(Str name, Int iq := 666) {
return Text.fromPlain("Hello! I'm $name and I have an IQ of $iq!")
}
}
</pre></pre>
<p>And for completeness, here is my <code>build.fan</code></p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using build
class Build : BuildPod {
new make() {
podName = "afBedSheetExt"
summary = "My Awesome BedSheetExt Project"
version = Version("1.0")
index = [
"skyarc.ext": "afBedSheetExt::BedSheetExt"
]
depends = [
// ---- Core Fantom ----
"sys 1.0.69 - 1.0",
"web 1.0.69 - 1.0",
"concurrent 1.0.69 - 1.0",
// ---- Sky Spark ----
"skyarcd 3.0.6 - 3.0",
// ---- BedSheet ----
"afIoc 3.0.4 - 3.0",
"afBedSheet 1.5.2 - 1.5",
]
srcDirs = [`fan/`]
resDirs = [`locale/`]
}
}
</pre></pre>
<p>It was then a simple case of building the pod, adding it to SkySpark and enabling the <code>ext</code> in an example project.</p>
<p>I then used the following URLS to test the BedSheet application:</p>
<pre class="syntax microlight notForIe ">C:\> curl http://localhost:8080/api/example/ext/bedsheet/
<html><body>Welcome to BedSheet!</body></html>
C:\> curl http://localhost:8080/api/example/ext/bedsheet/hello/Traci/69
Hello! I'm Traci and I have an IQ of 69!
C:\> curl http://localhost:8080/api/example/ext/bedsheet/hello/Luci
Hello! I'm Luci and I have an IQ of 666!
</pre></pre>
<p>And I could see the usual re-assuring Alien-Factory logo in the SkySpark console window.</p>
<p>Note that BedSheet has it's own pod dependencies which also need copying to SkySpark's <code>/lib/fan/</code> dir:</p>
<ul class="blendChildren">
<li><a href="http://eggbox.fantomfactory.org/pods/afBeanUtils/">afBeanUtils</a></li>
<li><a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">afBedSheet</a></li>
<li><a href="http://eggbox.fantomfactory.org/pods/afConcurrent/">afConcurrent</a></li>
<li><a href="http://eggbox.fantomfactory.org/pods/afIoc/">afIoc</a></li>
<li><a href="http://eggbox.fantomfactory.org/pods/afIocConfig/">afIocConfig</a></li>
<li><a href="http://eggbox.fantomfactory.org/pods/afIocEnv/">afIocEnv</a></li>
</ul>
<p>But how you may maintain and distribute these with your SkySpark application is an entirely different problem!</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>29 April 2017</em> - Posted article on Alien-Factory.</li>
<li><em>18 October 2016</em> - Original article posted on <a href="https://skyfoundry.com/forum/topic/2438">SkyFoundry Forum</a>.</li>
</ul>
http://www.alienfactory.co.uk/articles/2-way-password-encryption
2 Way Password Encryption in Fantom
2017-04-10T00:00:00+02:00
2017-04-10T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead">How to perform secure 2-way password encryption in Fantom</p>
<ol class="blendChildren" style="list-style-type: decimal;">
<li><a href="#overview">Overview</a></li>
<li><a href="#AESEncryption">AES Encryption</a></li>
<li><a href="#keyGeneration">Key Generation</a></li>
<li><a href="#unlimitedStrengthJurisdictionPolicy">Unlimited Strength Jurisdiction Policy</a></li>
<li><a href="#encryption">Encryption</a></li>
<li><a href="#decryption">Decryption</a></li>
<li><a href="#fullExample">Full Example</a></li>
<li><a href="#references">References</a></li>
</ol>
<h2 id="overview">Overview</h2>
<p>If you're managing passwords then usually the first rule is, don't manage passwords! (Keep a secure one-way hash instead.) If in doubt, this great little article tells it all in simple terms:</p>
<p><a href="https://adambard.com/blog/3-wrong-ways-to-store-a-password/">3 Wrong Ways to Store a Password</a></p>
<p>But some times, you just need to keep hold of the original password.</p>
<p>For instance, if you're acting as the middle man and need to send the password up to a remote server to login to it; as I did recently.</p>
<p>I decided to keep the password in a plain properties file for easy access, but I didn't want it stored as plain text for casual prying eyes. Hmmm....</p>
<p>So enter 2 way password encryption!</p>
<h2 id="AESEncryption">AES Encryption</h2>
<p>Advanced Encryption Standard (AES) Encryption is pretty good, and certainly good enough for what I wanted to use it for.</p>
<h2 id="keyGeneration">Key Generation</h2>
<p>AES requires a secret key. The more secure the secret key, the more secure the encryption.</p>
<p>A lot of code resources tell you how encrypt / decrypt text, but not many explain how to generate a decent secret key! It turns out that the <em>Password-Based Key Derivation Function #2</em> is way to go, requiring a pass phrase and a salt:</p>
<pre class="syntax microlight notForIe fantom">Str generateKey(Str passPhrase, Str salt) {
noOfBits := 128
noOfBytes := noOfBits / 8
iterations := 0x10000
return Buf.pbk("PBKDF2WithHmacSHA256", passPhrase, Buf().print(salt), iterations, noOfBytes).toBase64Uri
}
// --> g5_iBFSgSY9C36aUQTR6QQ
secretKey := generateKey("Fanny the Fantom", "Escape the Mainframe")
</pre></pre>
<p>We pass a plain text string in for the salt. A better salt would be a securely generated random binary number (at least 8 bytes) - but a string is easier to remember! (Note you need to pass in the same pass phrase and the same salt to generate the same secret key.)</p>
<p>Note the large iteration count above of <code>0x10000</code> / <code>65536</code>. This is how many times the <em>key derivation algorithm</em> is run; the larger the number, the more computational effort it takes to generate the key. This is a good thing as it prevents attackers from quickly trying many different passwords.</p>
<p>And then there is the generated key size - 128 bits. This is how long the returned secret key will be. From this we will have 128 bit AES encryption; which is pretty strong.</p>
<p>"Pah!" I hear you say. "I want a 256 bit key and 256 bit AES encryption!"</p>
<p>Well, you could. There's nothing stopping you... except for the:</p>
<h2 id="unlimitedStrengthJurisdictionPolicy">Unlimited Strength Jurisdiction Policy</h2>
<p>It turns out that US export laws prohibit Oracle from selling strong encryption. For your own comfort and safety, the government don't want Johnny Foreigner hiding their secrets. If you attempt to perform 256 bit AES encryption with a standard Java installation you'll receive a non-descript error of:</p>
<pre class="syntax microlight notForIe ">java.security.InvalidKeyException: Illegal key size</pre></pre>
<p>To get round it you need to install the <em>Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy</em>. It's freely available (see <a href="#references">References</a> below) but it's pain to do so and greatly restricts the distribution of your Java / Fantom program.</p>
<p>To play it safe, I opt to just using <em>strong</em> 128 bit AES encryption.</p>
<p>Note I don't believe this is an issue if using the free OpenJDK, just Oracle's JRE.</p>
<h2 id="encryption">Encryption</h2>
<p>Interestingly, AES encryption produces 2 outputs:</p>
<ul class="blendChildren">
<li>the cipher text, and</li>
<li>an initialisation vector</li>
</ul>
<p><em>Initialisation vector</em> is just a fancy word for <em>seed</em>, or <em>setup parameters</em>. It sets up AES so it can decrypt the cipher text. Note that <strong>both</strong> are required to decrypt the cipher text.</p>
<p>It is safe to distribute the initialisation vector, as it (and the cipher text) are useless without the original secret key. So I just base64 the two numbers and concatenate them with <code>::</code> characters. That way it's easy to separate them when it comes to decrypting.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using [java] fanx.interop::Interop
using [java] fanx.interop::ByteArray
using [java] java.lang::Class
using [java] java.nio::ByteBuffer
using [java] java.security::AlgorithmParameters
using [java] javax.crypto::Cipher
using [java] javax.crypto.spec::IvParameterSpec
using [java] javax.crypto.spec::SecretKeySpec
...
Str encode(Str secretKey, Str msg) {
keyBuf := Buf.fromBase64(secretKey)
keySpec := SecretKeySpec(toBytes(keyBuf), "AES")
cipher := Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, keySpec)
specClass := Class.forName("javax.crypto.spec.IvParameterSpec")
initVector := ((IvParameterSpec) AlgorithmParameters#getParameterSpec.call(cipher.getParameters, specClass)).getIV
cipherText := cipher.doFinal(toBytes(msg.toBuf))
return toBuf(initVector).toBase64Uri + "::" + toBuf(cipherText).toBase64Uri
}
private static ByteArray toBytes(Buf buf) {
Interop.toJava(buf).array
}
private static Buf toBuf(ByteArray array) {
Buf().writeBuf(Interop.toFan(ByteBuffer.wrap(array))).flip
}
</pre></pre>
<h2 id="decryption">Decryption</h2>
<p>To decrypt, we then just separate the initialisation vector from the actual cipher text.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using [java] fanx.interop::Interop
using [java] fanx.interop::ByteArray
using [java] java.nio::ByteBuffer
using [java] javax.crypto::Cipher
using [java] javax.crypto.spec::IvParameterSpec
using [java] javax.crypto.spec::SecretKeySpec
Str decode(Str secretKey, Str encoded) {
keyBuf := Buf.fromBase64(secretKey)
initVector := encoded[0..<encoded.index("::")]
cipherText := encoded[encoded.index("::")+2..-1]
keySpec := SecretKeySpec(toBytes(keyBuf), "AES")
cipher := Cipher.getInstance("AES/CBC/PKCS5Padding")
ivSpec := IvParameterSpec(toBytes(Buf.fromBase64(initVector)))
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
plainText := cipher.doFinal(toBytes(Buf.fromBase64(cipherText)))
return toBuf(plainText).readAllStr.trim
}
private static ByteArray toBytes(Buf buf) {
Interop.toJava(buf).array
}
private static Buf toBuf(ByteArray array) {
Buf().writeBuf(Interop.toFan(ByteBuffer.wrap(array))).flip
}
</pre></pre>
<p>And that's all you need for 2 way encryption!</p>
<h2 id="fullExample">Full Example</h2>
<p>Example usage:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">crypto := Crypto()
secretKey := crypto.generateKey("Fanny the Fantom", "Escape the Mainframe")
echo(secretKey) // --> g5_iBFSgSY9C36aUQTR6QQ
encode := crypto.encode(secretKey, "Hello Mum!")
echo(encode) // --> dX_PCw_XxUrc-EEh_6Q9Yw::7mR_15i-koItcc8Nbt_xvxG5FPL6ayvrWApRfTRPUxo
decode := crypto.decode(secretKey, encode)
echo(decode) // --> Hello Mum!
</pre></pre>
<p>Example Crypto code:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom huge">using [java] fanx.interop::Interop
using [java] fanx.interop::ByteArray
using [java] java.lang::Class
using [java] java.nio::ByteBuffer
using [java] java.security::AlgorithmParameters
using [java] javax.crypto::Cipher
using [java] javax.crypto.spec::IvParameterSpec
using [java] javax.crypto.spec::SecretKeySpec
const class Crypto {
Str secretKey(Str passPhrase, Str salt) {
noOfBits := 128
noOfBytes := noOfBits / 8
iterations := 0x10000
return Buf.pbk("PBKDF2WithHmacSHA256", passPhrase, Buf().print(salt), iterations, noOfBytes).toBase64Uri
}
Str encode(Str secretKey, Str msg) {
keyBuf := Buf.fromBase64(secretKey)
keySpec := SecretKeySpec(toBytes(keyBuf), "AES")
cipher := Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, keySpec)
// getParameterSpec() has some knarly Java generics which I can't figure out how to create in Fantom : "<T extends AlgorithmParameterSpec>"
// Note, java.lang.Class.asSubclass() does seem to work - maybe 'cos Fantom then assigns to a general 'Class' obj
// Anyway, just invoke it via reflection and all is okay
specClass := Class.forName("javax.crypto.spec.IvParameterSpec")
initVector := ((IvParameterSpec) AlgorithmParameters#getParameterSpec.call(cipher.getParameters, specClass)).getIV
cipherText := cipher.doFinal(toBytes(msg.toBuf))
// note the initVector is the same for repeated calls to getParameterSpec() but different for each instance of Cipher
return toBuf(initVector).toBase64Uri + "::" + toBuf(cipherText).toBase64Uri
}
Str decode(Str secretKey, Str encoded) {
keyBuf := Buf.fromBase64(secretKey)
initVector := encoded[0..<encoded.index("::")]
cipherText := encoded[encoded.index("::")+2..-1]
keySpec := SecretKeySpec(toBytes(keyBuf), "AES")
cipher := Cipher.getInstance("AES/CBC/PKCS5Padding")
ivSpec := IvParameterSpec(toBytes(Buf.fromBase64(initVector)))
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
plainText := cipher.doFinal(toBytes(Buf.fromBase64(cipherText)))
return toBuf(plainText).readAllStr.trim
}
private static ByteArray toBytes(Buf buf) {
Interop.toJava(buf).array
}
private static Buf toBuf(ByteArray array) {
// we can't base64 a NioBuf, so copy the contents to a standard Fantom MemBuf
Buf().writeBuf(Interop.toFan(ByteBuffer.wrap(array))).flip
}
}
</pre></pre>
<h2 id="references">References</h2>
<ul class="blendChildren">
<li><a href="https://adambard.com/blog/3-wrong-ways-to-store-a-password/">3 Wrong Ways to Store a Password</a> by Adam Bard</li>
<li><a href="http://stackoverflow.com/questions/992019/java-256-bit-aes-password-based-encryption#answer-992413">Java 256-bit AES Password-Based Encryption</a> on StackOverflow</li>
<li><a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">Advanced Encryption Standard</a> on Wikipedia</li>
<li><a href="http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html">(JCE) Unlimited Strength Jurisdiction Policy Files for Java 6</a> on Oracle</li>
<li><a href="http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html">(JCE) Unlimited Strength Jurisdiction Policy Files for Java 7</a> on Oracle</li>
<li><a href="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html">(JCE) Unlimited Strength Jurisdiction Policy Files for Java 8</a> on Oracle</li>
</ul>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>10 April 2017</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/fantom-faster-than-java-and-kotlin
Fantom - Faster than Java and Kotlin!
2014-06-16T00:00:00+02:00
2017-04-09T00:00:00+02:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead">Take a couple of exciting new JVM languages, add Java itself, sprinkle on a performance test and what do you get?</p>
<p><img class="img-fluid" src="/images/articles/2014/fantom-faster-than-java-and-kotlin.fannyVsDuke.png" alt="Fantom - Faster than Java!"></p>
<p class="lead"><strong>Some surprising results!</strong></p>
<p>In a test that performed calculations on financial instrument price movements, it was found that <a href="http://fantom-lang.org/">Fantom</a>, a language that compiles into Java byte code, was more than <em>twice the speed (x2)</em> of Java itself!</p>
<p><img class="img-fluid" src="/images/articles/2014/fantom-faster-than-java-and-kotlin.speed-chart.png" alt="Fantom is x2 faster than Java and x3 faster than Kotlin"></p>
<p><a href="http://kotlin.jetbrains.org/">Kotlin</a>, another language that compiles into Java byte code, was much much slower. In fact, it was more than 3 times slower than <a href="http://fantom-lang.org/">Fantom</a>, and as you might expect for a derivative language, was also slower than Java.</p>
<h2 id="how">How!?</h2>
<p>So how can <a href="http://fantom-lang.org/">Fantom</a>, a language based on Java, be faster than Java?</p>
<p>Although Fantom runs on the JVM, all of Fantom's core libraries have been written from scratch. Brian Frank, one of Fantom's creators, comments:</p>
<blockquote>
<p>There are definitely some libraries we've optimized the heck out of - especially the sys APIs.</p>
</blockquote>
<p>In fact, after inspecting the test code Brian goes on to mention that the Fantom test code is sub-optimal. And that with a couple of minor type changes, <em>it could be even faster!</em></p>
<h2 id="theTest">The Test</h2>
<p>The test processes a directory of stock data files taken from <a href="http://finance.yahoo.com/">finance.yahoo.com</a> and calculates the <a href="http://en.wikipedia.org/wiki/Open-high-low-close_chart">Open-high-low-close</a> data.</p>
<p>The test, along with the source code for Fantom, Java and Kotlin, is available on <a href="https://github.com/lel4866/FantomPerformance">GitHub</a>.</p>
<p>The full results, as reported on the <a href="http://fantom.org/forum/topic/2295">Fantom Discussion Forum</a>, are:</p>
<pre class="syntax microlight notForIe ">: Time Code Size
---------------------------------
Fantom 1.2 secs 105 lines
Java 8 2.9 secs 141 lines
Kotlin 4.2 secs 130 lines
</pre></pre>
<h2 id="moarDetails">Moar Details</h2>
<p>Read the full discussion on the Fantom Forum under <a href="http://fantom.org/forum/topic/2295">Fantom speed impressive...</a>.</p>
<p>And here you can read an official stance on <a href="http://fantom.org/forum/topic/1581">Fantom vs Kotlin (and other JVM langs)</a>.</p>
<p>Have fun!</p>
<h2 id="andByTheWay">Oh, and by the way...</h2>
<p>All the facts presented here are correct. The style however is a bit <a href="http://en.wikipedia.org/wiki/Tongue-in-cheek">tongue-in-cheek</a> and an attempt at writing a sensationalist tabloid article. Hope you liked it!</p>
<h2 id="edits:">Edits:</h2>
<ul class="blendChildren">
<li><em>9 April 2017</em> - Added Fanny vs Duke picture.</li>
<li><em>16 Jun 2014</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/create-a-mini-web-app-with-wisp
Create a Mini Web Application with Wisp
2016-12-30T00:00:00+01:00
2016-12-30T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead">A quick tutorial on how to create a mini web application with Fantom's <a href="http://fantom.org/doc/wisp/index.html">Wisp</a>.</p>
<p class="lead">A guide to serving web pages, posting forms, and uploading files.</p>
<p>Using Wisp is Fantom web programming at its most basic level. You have to do everything manually yourself from URL routing, through to setting the return <code>Content-Type</code> HTTP header.</p>
<p>It is the bare bones entry level API that is accessible to everyone, and as such, is useful to know before you move on to more powerful frameworks such as <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a> and <a href="http://eggbox.fantomfactory.org/pods/afPillow/">Pillow</a>.</p>
<p><code>wisp</code> and <code>web</code> are part of the core Fantom libraries and as such are bundled with SkySpark. So this tutorial may also interest SkySpark developers writing extensions.</p>
<p>For reference this tutorial was written with Fantom 1.0.69 and assumes the reader is already able to set up a Fantom project, build <code>.pod</code> files, and run code.</p>
<h2 id="contents">Contents</h2>
<ol class="blendChildren" style="list-style-type: decimal;">
<li><a href="#startingWisp">Starting Wisp</a></li>
<li><a href="#servingWebPages">Serving Web Pages</a></li>
<li><a href="#postingForms">Posting Forms</a></li>
<li><a href="#uploadingFiles">Uploading Files</a></li>
<li><a href="#tidyUp">Tidy Up - Use WebOutStream</a></li>
<li><a href="#completeExample">Complete Example</a></li>
</ol>
<h2 id="startingWisp">Starting Wisp</h2>
<p>Note that SkySpark developers don't need to worry about starting Wisp, as SkySpark obviously does that for us.</p>
<p>Wisp is Fantom's web server. To start it we're going to have our <code>Main</code> class extend <a href="http://fantom.org/doc/util/AbstractMain.html">util::AbstractMain</a> so we can use its <code>runServices()</code> method.</p>
<p>When creating a <a href="http://fantom.org/doc/wisp/WispService.html">WispService</a> we need to pass in an instance of a <a href="http://fantom.org/doc/web/WebMod.html">WebMod</a>. It is the <code>WebMods</code> responsibility to serve up web content. For now, we're just going to have every <code>GET</code> request return the text <code>Hello Mum!</code>. We do this by overriding the <code>onGet()</code> method.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using util::AbstractMain
using wisp::WispService
using web::WebMod
class Main : AbstractMain {
override Int run() {
runServices([WispService {
it.httpPort = 8069
it.root = MyWebMod()
}])
}
}
const class MyWebMod : WebMod {
override Void onGet() {
res.headers["Content-Type"] = "text/plain"
res.out.print("Hello Mum!")
}
}
</pre></pre>
<p>Running the program and pointing a browser at <code>http://localhost:8069/</code> then gives us:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.helloMumPlain.png" alt="Hello Mum! - plain text"></p>
<h2 id="servingWebPages">2. Serving Web Pages</h2>
<p>As great as plain text is, we need to serve up HTML pages. This can be accomplished by changing the returned <code>Content-Type</code> header to <code>text/html</code>.</p>
<p>We also need the ability to serve different pages from different URLs. A simple <code>switch</code> statement on the URL path offers basic routing and the ability to send 404 responses.</p>
<p>Our <code>MyWebMod</code> class now looks like:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">const class MyWebMod : WebMod {
override Void onGet() {
url := req.modRel.pathOnly
switch (url) {
case `/`:
onGetIndexPage()
default:
res.sendErr(404)
}
}
Void onGetIndexPage() {
res.headers["Content-Type"] = "text/html"
res.out.print("<!DOCTYPE html>
<html>
<head>
<title>Wisp Example</title>
</head>
<body>
<h1>Hello Mum!</h1>
</body>
</html>")
}
}
</pre></pre>
<p>Which serves up a page like this:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.helloMumHtml.png" alt="Hello Mum! - html"></p>
<h2 id="postingForms">3. Posting Forms</h2>
<p>Now lets post data to the server. To do that, we're going to change the index page html to incorporate a form:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe html"><!DOCTYPE html>
<html>
<head>
<title>Post Form Example</title>
</head>
<body>
<h1>Post Form</h1>
<form method='POST' action='/postForm'>
<label for='name'>Name</label>
<input type='text' name='name'>
<br/>
<label for='beer'>Beer</label>
<input type='text' name='beer'>
<br/>
<input type='submit' />
</form>
</body>
</html>
</pre></pre>
<p>Note the <em>method</em> attribute in the HTML form, <code>method="POST"</code>. This means the form will be <em>posted</em> to the server. If it were <code>GET</code> then the form values would be sent up as a query string.</p>
<p>Also note the <em>action</em> attribute in the HTML form, <code>action="/postForm"</code>. This is the URL that the form will be posted to, and need to be handled on the server.</p>
<p>Our page now looks like:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.postForm.png" alt="Post Form"></p>
<p>To handle a <code>POST</code> to <code>/postForm</code> we need to override the <code>onPost()</code> method. We'll write another <code>switch</code> statement, just as we did for <code>onGet()</code>:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">override Void onPost() {
url := req.modRel.pathOnly
switch (url) {
case `/postForm`:
onPostForm() // <-- need to implement this method
default:
res.sendErr(404)
}
}
</pre></pre>
<p>Now we'll implement <code>onPostForm()</code> and have it print out our form values. To access the form values, use <a href="http://fantom.org/doc/web/WebRes.html#form">WebRes.form()</a> which is just a handy string map of name / value pairs. Note that all submitted form values are of type <code>Str</code>.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">Void onPostForm() {
name := req.form["name"]
beer := req.form["beer"]
res.headers["Content-Type"] = "text/html"
res.out.print("<!DOCTYPE html>
<html>
<head>
<title>Post Form Values</title>
</head>
<body>
<p>Hello <b>${name}</b>, you like <b>${beer}</b>!</p>
</body>
</html>")
}
</pre></pre>
<p>If we submit our form, we should now see:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.postFormValues.png" alt="Post Form Values"></p>
<h2 id="uploadingFiles">4. Uploading Files</h2>
<p>As we saw, posting forms was easy. But how about uploading files? In other languages, such as Java, this can be notoriously tricky. But as you'll see, here in Fantom land it's still pretty simple.</p>
<p>First we need to add a file input to our form.</p>
<p>And note that for file uploads it is mandatory that we change form encoding by adding the attribute, <code>enctype="multipart/form-data"</code>.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe html"><!DOCTYPE html>
<html>
<head>
<title>Post Form Example</title>
</head>
<body>
<h1>Post Form</h1>
<form method='POST' action='/postForm' enctype='multipart/form-data'>
<label for='name'>Name</label>
<input type='text' name='name'>
<br/>
<label for='beer'>Beer</label>
<input type='text' name='beer'>
<br/>
<label for='photo'>Photo</label>
<input type='file' name='photo'>
<br/>
<input type='submit' />
</form>
</body>
</html>
</pre></pre>
<p>Which renders:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.uploadFile.png" alt="Upload File"></p>
<p>Handling form content with uploaded files on the server is a little more complicated due to the encoding. We can no longer use the handy <code>WebReq.form()</code> map, instead we have to use <code>WebReq.parseMultiPartForm()</code>. It takes a function which is invoked for every input in the form, both file uploads and normal values. The function gives us the input name, a stream of raw data, and any meta associated with the input.</p>
<p>Our <code>onPostForm()</code> now looks like:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">Void onPostForm() {
name := null as Str
beer := null as Str
photoName := null as Str
photoBuf := null as Buf
req.parseMultiPartForm |Str inputName, InStream in, Str:Str headers| {
if (inputName == "name")
name = in.readAllStr
if (inputName == "beer")
beer = in.readAllStr
if (inputName == "photo") {
quoted := headers["Content-Disposition"]?.split(';')?.find { it.lower.startsWith("filename") }?.split('=')?.getSafe(1)
photoName = quoted == null ? null : WebUtil.fromQuotedStr(quoted)
photoBuf = in.readAllBuf
}
}
res.headers["Content-Type"] = "text/html"
res.out.print("<!DOCTYPE html>
<html>
<head>
<title>Post Form Values</title>
</head>
<body>
<p>
Hello <b>${name}</b>, you like <b>${beer}</b>!
The photo <b>${photoName}</b> looks like:
</p>
<img src='data:${photoName.toUri.mimeType};base64,${photoBuf.toBase64}'>
</body>
</html>")
}
</pre></pre>
<p>We read the raw file data in as a <code>Buf</code>, and then spit it out as in inline Base64 encoded image. But you could do whatever you like with it; read CSV values, save it to a database...</p>
<p>The long line of code that grabs the <code>quoted</code> string:</p>
<pre class="syntax microlight notForIe fantom">quoted := headers["Content-Disposition"]?.split(';')?.find {
it.lower.startsWith("filename")
}?.split('=')?.getSafe(1)
</pre></pre>
<p>is a means to parse the <code>Content-Disposition</code> header, which looks like this:</p>
<pre class="syntax microlight notForIe ">Content-Disposition: form-data; name="photo"; filename="beer.png"</pre></pre>
<p>As you can see, it contains the name of the uploaded file which can be very handy. Note that browsers typically only send up a file name and not the entire path. This is a security consideration so servers don't learn anything of the client computer.</p>
<p>The net result is that we end up with this web page:</p>
<p><img class="img-fluid" src="/images/articles/2016/bvw01-create-a-mini-web-app-with-wisp.uploadFileValues.png" alt="Upload File Values"></p>
<h2 id="tidyUp">Tidy Up - Use WebOutStream</h2>
<p>Not every one is a fan of long, multi-line strings for rendering HTML. An alternative is to use the print methods on the <a href="http://fantom.org/doc/web/WebOutStream.html">WebOutStream</a>. It's a means to print HTML in a more programmatic way without having to worry about leading tabs and spaces.</p>
<p>Here is our <code>onGetIndexPage()</code> method refactored to make use of <code>WebOutStream</code>:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">Void onGetIndexPage() {
res.headers["Content-Type"] = "text/html"
out := res.out
out.docType5
out.html
out.head
out.title.w("Post Form Example").titleEnd
out.headEnd
out.body
out.h1.w("Post Form").h1End
out.form("method='POST' action='/postForm' enctype='multipart/form-data'")
out.label("for='name'").w("Name").labelEnd
out.input("type='text' name='name'")
out.br
out.label("for='beer'").w("Beer").labelEnd
out.input("type='text' name='beer'")
out.br
out.label("for='photo'").w("Photo").labelEnd
out.input("type='file' name='photo'")
out.br
out.submit
out.formEnd
out.bodyEnd
out.htmlEnd
}
</pre></pre>
<p>I would say that using <code>WebOutStream</code> is no better or worse than multi-line strings, just different.</p>
<h2 id="completeExample">6. Complete Example</h2>
<p>Below is the complete example that:</p>
<ul class="blendChildren">
<li>Starts the Wisp web server</li>
<li>Has basic routing for page URLs</li>
<li>Displays a HTML index page, complete with a file upload form</li>
<li>Has basic routing for form posts</li>
<li>Parses uploaded form data</li>
<li>Displays the data in a new HTML page</li>
<li>Uses <code>WebOutStream</code></li>
</ul>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom huge">using util::AbstractMain
using wisp::WispService
using web::WebMod
using web::WebUtil
class MainWisp : AbstractMain {
override Int run() {
runServices([WispService {
it.httpPort = 8069
it.root = MyWebMod()
}])
}
}
const class MyWebMod : WebMod {
override Void onGet() {
url := req.modRel.pathOnly
switch (url) {
case `/`:
onGetIndexPage()
default:
res.sendErr(404)
}
}
override Void onPost() {
url := req.modRel.pathOnly
switch (url) {
case `/postForm`:
onPostForm()
default:
res.sendErr(404)
}
}
Void onGetIndexPage() {
res.headers["Content-Type"] = "text/html"
out := res.out
out.docType5
out.html
out.head
out.title.w("Post Form Example").titleEnd
out.headEnd
out.body
out.h1.w("Post Form").h1End
out.form("method='POST' action='/postForm' enctype='multipart/form-data'")
out.label("for='name'").w("Name").labelEnd
out.input("type='text' name='name'")
out.br
out.label("for='beer'").w("Beer").labelEnd
out.input("type='text' name='beer'")
out.br
out.label("for='photo'").w("Photo").labelEnd
out.input("type='file' name='photo'")
out.br
out.submit
out.formEnd
out.bodyEnd
out.htmlEnd
}
Void onPostForm() {
name := null as Str
beer := null as Str
photoName := null as Str
photoBuf := null as Buf
req.parseMultiPartForm |Str inputName, InStream in, Str:Str headers| {
if (inputName == "name")
name = in.readAllStr
if (inputName == "beer")
beer = in.readAllStr
if (inputName == "photo") {
quoted := headers["Content-Disposition"]?.split(';')?.find { it.lower.startsWith("filename") }?.split('=')?.getSafe(1)
photoName = quoted == null ? null : WebUtil.fromQuotedStr(quoted)
photoBuf = in.readAllBuf
}
}
res.headers["Content-Type"] = "text/html"
out := res.out
out.docType5
out.html
out.head
out.title.w("Post Form Values").titleEnd
out.headEnd
out.body
out.p
out.w("Hello ").b.w(name).bEnd.w(", you like ").b.w(beer).bEnd.w("! ")
out.w("The photo ").b.w(photoName).bEnd.w(" looks like:")
out.pEnd
out.img(`data:${photoName.toUri.mimeType};base64,${photoBuf.toBase64}`)
out.bodyEnd
out.htmlEnd
}
}
</pre></pre>
<p>Have fun!</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>30 Dec 2016</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/bedSheet-the-evolution-of-wisp
BedSheet - the Evolution of Wisp
2016-12-28T00:00:00+01:00
2016-12-28T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p><a href="http://fantom.org/doc/wisp/index.html">Wisp</a> is the bare bones, entry level API for web programming that is accessible to everyone, including SkySpark developers. So it is the natural choice when it comes to serving web pages.</p>
<p>Wisp is one of the core Fantom libraries so comes pre-bundled with every Fantom download.</p>
<p><a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a> is more of an application framework and acting IoC container. It is pluggable, customisable, and massively extensible. BedSheet itself runs as a <code>WebMod</code> within Wisp, and so offers an advanced means of serving content over HTTP.</p>
<p>BedSheet works seemslessly along side Wisp and may be downloaded from the <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">Eggbox pod repository</a>.</p>
<p>The following articles write the same mini web application using both technologies. They serve as a light introduction to the libraries, helping to ease you into their respective APIs.</p>
<ol class="blendChildren" style="list-style-type: decimal;">
<li><a href="/articles/create-a-mini-web-app-with-wisp">Create a Mini Web Application with Wisp</a>
<p>Serve HTML page, post forms, and upload files using Fantom's core libraries.</p>
</li>
<li><a href="/articles/create-a-mini-web-app-with-bedsheet">Create a Mini Web Application with BedSheet</a>
<p>The same mini web app is re-written using the Alien-Factory BedSheet suite of libraries.</p>
</li>
</ol>
<p>Comparing <a href="/articles/create-a-mini-web-app-with-bedsheet#completeExample">BedSheet code</a> of the mini-app with the <a href="/articles/create-a-mini-web-app-with-wisp#completeExample">Wisp code</a> of the same heralds about a 40% reduction in code (~60 lines vs ~100 lines).</p>
<p>There's also a better separation of concerns with respect to page classes, data classes, and templates.</p>
<p>Not to mention the added benefits of BedSheet with regards to development; no application re-starts (just refresh the browser page), extensive error and 404 handling, and dedicated testing frameworks.</p>
<p>BedSheet also has a wealth of pluggable libraries to aid you further - see the <a href="http://eggbox.fantomfactory.org/pods/?tags=web">Eggbox Pod Repository</a> for details. As such it takes only a few minutes to add site maps, RSS feeds, and asset caching to your web application!</p>
<p>So what are you waiting for? Evolve your Wisp today!</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>28 Dec 2016</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/fantom-the-developers-choice
Fantom - The Developer's Choice!
2016-12-10T00:00:00+01:00
2016-12-10T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p><div class="noPrint"><iframe width="600" height="450" src="https://www.youtube.com/embed/klC9kCftp9A?showinfo=0" frameborder="0" allowfullscreen style="border: 4px solid #20ee23;"></iframe></div><div class="text-xs-center" style="width: 600px"><a href="https://www.youtube.com/watch?v=klC9kCftp9A" class="lead">Watch Video on YouTube </a></div></p>
<p class="lead">A 15 minute talk on the differences between the Fantom programming language and Java, showing some of the reasons why Fantom is a real viable alternative to writing pure Java.</p>
<p>Presented to the SkyFoundry Partner Meeting in Washington DC on Wednesday 30 November 2016.</p>
<p>The presentation has been reproduced in the article below, the contents of each are the same.</p>
<h2 id="whatIsFantom?">What is Fantom?</h2>
<p class="lead">Fantom is a Future Proof, Next Generation Language.</p>
<p>Now, that's a bold statement so we're going to look at some examples of why I think it to be true.</p>
<p>But to do so, we'll have to compare Fantom against a normal, current generation language. For that we're going to use Java.</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.javaDuke.png" alt="Java's Duke Mascot"></p>
<p>I've picked Java because it's a popular language many will be familiar with.</p>
<p>But who am I to be making this comparison? Well, I would call myself a full stack, web application developer. I have a lot of experience with many languages including, of course, Fantom. Java in particular I've used for most of my career, and have over 13 continuous years of Java development.</p>
<p>But given an option, I wouldn't use most of these languages. Not that I particularly dislike them, but I do consider them to be:</p>
<pre class="syntax microlight notForIe ">Assembler - Tedious
Visual Basic - Limited
Java - Complicated
Javascript - Anti-Agile
Ruby - Dangerous
C# - Ugly
</pre></pre>
<p>Which leaves...</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.fanny-x128.png" alt="Fanny the Fantom"></p>
<p class="lead">Fantom!</p>
<p>And to prove it, we're going to look at the topics of:</p>
<ol class="blendChildren" style="list-style-type: decimal;">
<li><a href="#literals">Literals</a>
<ul class="blendChildren">
<li><a href="#integers">Integers</a></li>
<li><a href="#durations">Durations</a></li>
<li><a href="#uris">URIs</a></li>
</ul>
</li>
<li><a href="#coreApi">Core API</a>
<ul class="blendChildren">
<li><a href="#lists">Lists</a></li>
<li><a href="#streams">Streams</a></li>
<li><a href="#characterSets">Character Sets</a></li>
</ul>
</li>
<li><a href="#concurrency">Concurrency</a>
<ul class="blendChildren">
<li><a href="#javaMutableState">Java - Mutable State</a></li>
<li><a href="#fantomMessageProcessing">Fantom - Message Processing</a></li>
</ul>
</li>
<li><a href="#languageSugar">Language Sugar</a>
<ul class="blendChildren">
<li><a href="#fieldAccessors">Field Accessors</a></li>
<li><a href="#typeInference">Type Inference</a></li>
<li><a href="#codeVisibility">Code Visibility</a></li>
<li><a href="#honourableMentions">Honourable Mentions</a></li>
</ul>
</li>
<li><a href="#crossPlatformDevelopment">X-Platform Development</a>
<ul class="blendChildren">
<li><a href="#fannyTheFantom">Fanny the Fantom</a></li>
</ul>
</li>
</ol>
<h2 id="literals">1. Literals</h2>
<p class="lead">Fantom is a Forward Thinking Language</p>
<p>Let's see how.</p>
<h3 id="integers">Integers</h3>
<p>In Java, if you want to represent a whole number you have a crazy amount of choices!</p>
<pre class="syntax microlight notForIe ">Java Integers:
byte - 8 bits
short - 16 bits
char - 16 bits
int - 32 bits
long - 64 bits
</pre></pre>
<p>And in C# you even have signed and unsigned choices!</p>
<p>Woah! I don't care! I have a number, let's say <code>10</code>. Perhaps it'll change to <code>2,000</code>, maybe <code>200,000</code>. I don't know, I don't want to pre-calculate the min max range!</p>
<p>The thing is, all of this unwanted choice is to deal with legacy architectures from a time when it wasn't efficient to perform 64 bit addition. And that in-efficiency was mocked by C and assembler developers, even if it didn't matter in the grand scheme of programming.</p>
<p>More to the point in today's world of 64 bit architecture supremacy, it isn't even more efficient to process smaller bit numbers! Some even argue it takes <em>longer</em> because of extra bit masking that may be involved! <a href="#ref-1">(1)</a></p>
<p>So what of Fantom then?</p>
<pre class="syntax microlight notForIe ">Fantom Integers:
Int - 64 bits
</pre></pre>
<p>It has one <code>Int</code> that's 64 bits.</p>
<p>Job done.</p>
<p>Would you ever need more bits? Realistically, for general purpose numbers, probably not. 64 bits is pretty huge!</p>
<p>Fantom stores time in an <code>Int</code> - it represents NANO seconds since the year 2000, and that gives us +/-292 years at a pretty high precision. <a href="#ref-2">(2)</a> Especially as:</p>
<pre class="syntax microlight notForIe ">1 ms == 1,000,000 ns</pre></pre>
<p>See this comparison on how dates are stored:</p>
<pre class="syntax microlight notForIe ">Java: milli seconds since 1970 (unix epoc)
Fantom: nano seconds since 2000 (millennium)
</pre></pre>
<p>That's pretty forward thinking!</p>
<h3 id="durations">Durations</h3>
<p>When it comes to literals - that is, types understood by the compiler, most languages only recognise numbers, booleans, and strings.</p>
<p>So lets talk about a pet hate of mine.</p>
<p>What's wrong with this Java code?</p>
<pre class="syntax microlight notForIe java">int countdown = 10;
System.out.println("This spaceship will self destruct in " + countdown);
</pre></pre>
<p>Well, it's going to print,</p>
<pre class="syntax microlight notForIe ">This spaceship will self destruct in 10</pre></pre>
<p>10! 10 what!?</p>
<p>10 seconds? 10 hours? What!? Damn it, units are important! <em>Especially if my spaceship is about to explode!</em></p>
<p>But more realistically, I see this a lot in APIs when setting timeouts - it leaves the developer guessing whether to pass in minutes, seconds, or milli-seconds. Get it wrong and your 1 second timeout may turn into 16½ minutes!</p>
<p>So Fantom introduces a handy <code>Duration</code> literal. Meaning if you add the word <code>sec</code> or a <code>min</code> to a number, the Fantom compiler doesn't create an instance of a number. Instead it creates an instance of a <code>Duration</code> class.</p>
<p>Now I know exactly when my spaceship will explode!</p>
<p>Which is good news for some!</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.dropShip.png" alt="Joyous SpaceMen!"></p>
<h3 id="uris">URIs</h3>
<p>Fantom doesn't stop there. It also has a URI literal. Use back ticks instead of normal quotes to create an instance of a URI class.</p>
<pre class="syntax microlight notForIe fantom">url := `http://fantom-lang.org/download`</pre></pre>
<p>It may seem strange at first, but think about it. We're in the age of the Internet. <em>The Internet of Things!</em></p>
<p>It not just web pages that have URLs any more. There are images, sounds, REST APIs, databases, and even local file systems!</p>
<p>But it's not just URLs, the Fantom literal is a UR-I (Universal Resource Indicator) meaning, much like how XML is to HTML, you can use it to create your own schemes and IDs!</p>
<p>Why use simple numbers or strings as database IDs when you could be using recognisable and hierarchical URIs!?</p>
<p>For example, why use the obscure number <code>36</code> when you could use an URI that comes complete with useful metadata such as what application it belongs to and what collection it represents?</p>
<pre class="syntax microlight notForIe fantom">Int '36' vs URI `myApp:/pages/36`</pre></pre>
<h2 id="coreApi">2. Core API</h2>
<p class="lead">Fantom's Concise and Sane API is a Developer's Dream!</p>
<p>That's nice! But first let's look at what's not a developer's dream!</p>
<h3 id="lists">Lists</h3>
<p>The Java Collections Framework!</p>
<p>Say you want a simple list of objects in Java. Arrays are fixed and too inflexible, so you opt for a <code>List</code> object. But do you want a:</p>
<pre class="syntax microlight notForIe ">Java Lists:
java.util.Collection
java.util.Set
java.util.SortedSet
java.util.NavigableSet
java.util.List
java.util.Queue
java.util.Deque
</pre></pre>
<p>And that's just the Interfaces! From there you have to choose an implementation! It is Array based? Linked, hashed, or tree based!? Synchronized or fail fast!? The mind boggles!</p>
<p>In all there are about 45 classes and interfaces just for lists and maps in Java! <a href="#ref-3">(3)</a></p>
<p>If you think all this is okay then look at this...</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.javaLists.png" alt="Simple View of Java Lists"></p>
<p>The Java Ultimate website presents this <strong>Simple View</strong> of a List! <a href="#ref-4">(4)</a></p>
<p>Boy, I'd hate to see the complicated view!</p>
<p>I just want an ordered list of 10 things. I really shouldn't need a computer science degree to choose the right implementation!</p>
<p>So by comparison, lets see what Fantom has to offer.</p>
<pre class="syntax microlight notForIe ">Fantom Lists:
sys::List
</pre></pre>
<p>It has one.</p>
<p>It handles generics, it has functional methods with closures, and all the methods you need.</p>
<p>Especially for a new comer to Fantom, or programming, it's easy to remember and easy to use.</p>
<p>Job done.</p>
<h3 id="streams">Streams</h3>
<p>Another Java example, this time with Streams.</p>
<p>The <code>java.io</code> package has some 80 classes! <a href="#ref-5">(5)</a></p>
<p>Fantom has 2. An <code>InStream</code>, and an <code>OutStream</code>.</p>
<p>Again, they're functional, use closures and have all the methods you need.</p>
<p>Easy to remember, easy to use.</p>
<p>Let's put some of these 80 Java classes to work and read a plain text file.</p>
<p>This is the accepted answer from a StackOverflow question on how to read a plain text file in Java. <a href="#ref-6">(6)</a></p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe java">// How to read a plain text file in Java
FileReader fr = new FileReader("file.txt")
BufferedReader br = new BufferedReader(fr);
try {
StringBuilder sb = new StringBuilder();
String line = br.readLine();
while (line != null) {
sb.append(line);
sb.append(System.lineSeparator());
line = br.readLine();
}
String everything = sb.toString();
} finally {
br.close();
}
</pre></pre>
<p>It's 14 lines long! How complicated and convoluted is that!?</p>
<p>Lets look at Fantom...</p>
<pre class="syntax microlight notForIe fantom">// How to read a plain text file in Fantom
everything := File(`file.txt`).readAllStr
</pre></pre>
<p>One line. It's that simple!</p>
<p>This is not just some handy utility method, this the core API and it is one small example of the thought gone into making common tasks really simple.</p>
<p>Note how we use a URI to represent a file on the file system!</p>
<h3 id="characterSets">Character Sets</h3>
<p>On the subject of text files, what of character sets and text encoding?</p>
<p>The great Joel Spolsky once said:</p>
<p class="lead"><em>"There is no such thing as plain text"</em> - Joel Spolsky <a href="#ref-7">(7)</a></p>
<p>But lets face it, UTF-8 comes pretty close. And thankfully, Fantom defaults to UTF-8 for everything text related and hence truly becomes platform independent.</p>
<p>Note that Java gets Characters Sets very wrong and uses a platform default, meaning (unless you're very, very careful) you end up with incompatible text files on different systems. Even the Maven build tool fell foul of this one! <a href="#ref-8">(8)</a></p>
<h2 id="concurrency">3. Concurrency</h2>
<p class="lead">Fantom uses Actors and the Message Processing Model</p>
<p>This is quite a technical topic that really deserves its own presentation to do it justice, but it is also too important not to mention. So we'll just skim over some of the main points.</p>
<h3 id="javaMutableState">Java - Mutable State</h3>
<p>First we'll look at Java's stance on concurrency; it uses mutable state.</p>
<p>Mutable state is simple enough to understand. each thread has access to the same bit of data and each thread may update it any time.</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.javaThreads.png" alt="Java Threads"></p>
<p>Unfortunately java tries to be clever and introduces things like thread data caching. And what happens when you need to update 2 bits of data at the same time and don't want any other thread to access any of it until you're finished!? (Like the contrived example of crediting one bank account and debiting the other).</p>
<p>All this and more, means you need to be an expert in mutable concurrency models just to implement the basics! <a href="#ref-9">(9)</a></p>
<h4 id="difficultToImplementSafely">Difficult to Implement Safely</h4>
<p>In brief, mutable state in Java is difficult to implement safely because to do it properly you also need to fully understand:</p>
<ul class="blendChildren">
<li>Threads,</li>
<li>Locks,</li>
<li>Semaphores,</li>
<li>JSR133,</li>
<li>Broken double locking mechanisms,</li>
<li>and more!</li>
</ul>
<p>Functional programming says that <em>"All state is evil!"</em> If that's true then surely mutable state must be the ultimate evil!</p>
<h4 id="errorProne">Error Prone</h4>
<p>Java has 60 classes dedicated to doing concurrency properly. <a href="#ref-10">(10)</a> Given all the complications of the above, then implementing mutable state safely is also prone to errors and mistakes.</p>
<h4 id="notScalable">Not Scalable</h4>
<p>And it's not even scalable. If you wanted more processing threads then you're just creating a bottle neck on the synchronised data.</p>
<h3 id="fantomMessageProcessing">Fantom - Message Processing</h3>
<p>By comparison, Fantom employs an Actor framework and the message processing model.</p>
<p>Here threads, also known as Actors, send messages to each other. These messages are just instances of Fantom classes. But not just any classes, immutable classes! This means the message content can not change, hence we don't have the mutable state conundrum that Java has.</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.fantomActors.png" alt="Fantom Actors"></p>
<h4 id="easyToImplementSafely">Easy to Implement Safely</h4>
<p>Better yet, it is not even possible to compile a Fantom program whereby different threads have access to the same bit of data! Therefore when it comes to concurrency, Fantom as a language is inherently safe!</p>
<p>Actors are easy to understand and implement and it only take 4 classes in the Fantom <code>concurrent</code> pod.</p>
<h4 id="lessErrorProne">Less Error Prone</h4>
<p>The <em>compiler</em> forces the use of immutable message classes between Actors which makes it safe and less prone to errors.</p>
<h4 id="scalable">Scalable</h4>
<p>The Actor model is also scalable, allowing you to add more threaded workers as you wish.</p>
<h2 id="languageSugar">4. Language Sugar</h2>
<p class="lead">Fantom's Language Sugar makes it a joy to program!</p>
<p>There are sooo many little things to mention here I've found it difficult to limit myself - but I've picked 3 to share with you...</p>
<h3 id="fieldAccessors">Field Accessors</h3>
<p>Java has fields. But if you wanted to alter the behaviour slightly, like trimming and a validating inputs, then you would need a method. And then you'd need to change ALL your code from using the field to using the method.</p>
<p>As this is painful, Java programmers usually forgo fields altogether and instead create lots of boiler plate getter and setter methods. <a href="#ref-11">(11)</a></p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe java">// Example Java field with getter / setter
private String greeting;
public String getGreeting() {
return "Hello " + greeting + "!";
}
public void setGreeting(String greeting) {
this.greeting = greeting.trim();
}
// code to set a field value changes from ...
myClass.greeting = "Mum";
// to ...
myClass.setGreeting("Mum");
</pre></pre>
<p>It's tedious.</p>
<p>Fantom, on the other hand, has real field accessors! Something I've missed since my early days of programming Visual Basic! Not even C# properties come close to the real power of Fantom fields!</p>
<p>Here, we have examples of 3 Fantom fields.</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">// standard field
Str greeting1
// field with getter / setter
Str greeting2 {
get { "Hello ${&greeting}!" }
set { &greeting = it.trim }
}
// read only field
Str greeting3 {
private set
}
</pre></pre>
<p>Note that the second has a getter and setter. These are optional, any field may or may not have them. It doesn't matter if they exist or not, all the code that uses that field stays the same.</p>
<p>Even better, if you want to make the field read only, just make the setter private!</p>
<p>No boiler plate code, no getter and setter methods. Just intelligent fields.</p>
<h3 id="typeInference">Type Inference</h3>
<p>Next a quick look at the Java syntax.</p>
<p>Here we create a new StringBuilder class, and we assign it to a StringBuilder variable.</p>
<pre class="syntax microlight notForIe java">// create a Java variable
StringBuilder sb = new StringBuilder();
</pre></pre>
<p>Note how we've just had to type <code>StringBuilder</code> twice.</p>
<p>And this happens a lot in Java. It's strongly typed and we have to repeat ourselves over and over and over... which makes for long lines of code. Especially when you start adding in generics!</p>
<p>Fantom is different. It uses <em>Type Inference</em> to determine what the variables should be.</p>
<p>Here we create a <code>StrBuf</code> (similar to java's <code>StringBuilder</code>) but Fantom knows what it is and hence assigns it to automatically to a <code>StrBuf</code> variable.</p>
<pre class="syntax microlight notForIe fantom">// create a Fantom variable
sb := StrBuf()
</pre></pre>
<p>We don't repeat ourselves and it cuts down a lot of code.</p>
<p>Other languages have cottoned on to the concise expressiveness of type inference, but Fantom gives real power to the programmer and goes one step further...</p>
<p>It has a unique "loose" type inference whereby if something <em>could</em> work then it assumes it <em>does</em> work!</p>
<p>A quick example with 3 classes to explain it.</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.animalClass.png" alt="Animal class with Cat and Dog subclasses"></p>
<p>Assume we have a method <code>eat()</code> that takes a Cat:</p>
<pre class="syntax microlight notForIe fantom">Void eat(Cat cat) { ... }
// ✔ - passing in a Cat instance is fine
cat := Cat()
eat(cat)
</pre></pre>
<p>The compiler will bork if we try to pass in a Dog:</p>
<pre class="syntax microlight notForIe fantom">Void eat(Cat cat) { ... }
// ✗- compilation error when passing a Dog instance
dog := Dog()
eat(dog)
</pre></pre>
<p>But the compiler <em>will</em> let us pass in an animal instance, even if it is wrong.</p>
<pre class="syntax microlight notForIe fantom">Void eat(Cat cat) { ... }
// ? - runtime error if animal is not a Cat
animal := Dog() as Animal
eat(animal)
</pre></pre>
<p>This will, of course, cause a runtime error. But because it <em>could</em> work, the compiler trusts that the programmer knows what they're doing and inserts an implicit cast. (Java would force us to explicitly add the cast ourselves.)</p>
<p>But in the course of static programming, 99.9% of the time we do <em>know</em> what our variables are. And in practice, this is never an issue.</p>
<p>But what it does do for us, is dramatically cut down the amount of casting and type coercion, meaning we type a heck of a lot less!</p>
<h3 id="codeVisibility">Code Visibility</h3>
<p>This is a such a small thing, but it means so much to me because I write so many libraries and pods for Fantom.</p>
<p>Fantom has 3 main visibility types:</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.fantomVisibility.png" alt="Fantom Visibility"></p>
<p>Private visibility means it is private to the class.</p>
<p>Public visibility means everyone can see it. This is the public API that your library exposes to the outside world.</p>
<p>Then there's <em>internal</em> visibility that means it is only accessible to the containing pod.</p>
<p>This is brilliant because it means you can pick and choose what to share with the outside world and keep all your <em>internals</em> hidden from view! For example, the Sizzle pod which performs CSS selectors on XML documents, it has a tiny public API of 1 class and 6 methods. But internally it has 10 classes and countless methods! <a href="#ref-12">(12)</a></p>
<p>A common problem in other languages, like Java, is that they expose <em>everything</em> which makes it very daunting for users to know which classes and methods they should be using!</p>
<h3 id="honourableMentions">Honourable Mentions</h3>
<p>Other cool language sugar that I could mention include:</p>
<ul class="blendChildren">
<li>Default method parameters</li>
<li>Everything is an Object</li>
<li>Nullable types</li>
<li>No checked exceptions</li>
<li>Simple reflection</li>
<li>Dynamic programming</li>
<li>Human readable object serialisation</li>
<li>Markdown documentation</li>
<li>...</li>
</ul>
<p>In fact, there are just too many to mention!</p>
<h2 id="crossPlatformDevelopment">5. X-Platform Development</h2>
<p class="lead">Fantom for Cross Platform Development</p>
<p>Fantom runs great on a Java JVM, and there's a lot of work gone into a .NET implementation. But annotate any class with <code>@Js</code> and it automatically gets compiled to Javascript. That's right, the entire Fantom runtime and <code>sys</code> pod is available in Javascript, and runs in a browser!</p>
<p>If you want to specifically write Fantom that targets the browser environment, then the <code>dom</code> and <code>domkit</code> libraries give you some kick ass performance.</p>
<p>But better yet, the core Fantom graphics library, and the windowing toolkit, initially written for desktops, also runs in a browser! I can't tell you how cool this is! In fact, I can only show you!</p>
<h3 id="fannyTheFantom">Fanny the Fantom</h3>
<p>So here is a little game I created. I wrote it in Fantom and I wrote the entire game to run as a desktop application. I then set about making it run in a browser. To do that took just 25 minutes, including creating a web site to serve it from!</p>
<p><img class="img-fluid" src="/images/articles/2016/fantom-the-developers-choice.fannyShot.png" alt="Fanny the Fantom Title Screen"></p>
<p>It is called <em>Fanny the Fantom</em>, features some retro looking vector graphics, and even persists its high scores to a server on the internet! <a href="#ref-13">(13)</a></p>
<p>Play with Fanny in a browser here:</p>
<p class="lead"><a href="http://fanny.fantomfactory.org/">Fanny the Fantom Game</a></p>
<h2 id="epilogue">Epilogue</h2>
<p>To find out more about Fantom you can visit:</p>
<p class="lead"><a href="http://fantom.org/">http://fantom.org/</a> (official site)</p>
<p class="lead"><a href="http://fantom-lang.org/">http://fantom-lang.org/</a> (community site)</p>
<p>I am Steve Eynon.</p>
<p>And this has been why I consider Fantom to be the Developers Choice!</p>
<h2 id="references">References</h2>
<ol class="blendChildren" style="list-style-type: decimal;">
<li id="ref-1"> StackOverflow - <a href="http://stackoverflow.com/a/163272/1532548">On 32-bit CPUs, is an 'int' type more efficient than a 'short' type?</a></li>
<li id="ref-2"> Fantom - <a href="http://fantom.org/doc/docLang/DateTime#tradeoffs">DateTime Trade Offs</a></li>
<li id="ref-3"> Oracle - <a href="https://docs.oracle.com/javase/8/docs/api/java/util/package-summary.html">java.util package summary</a></li>
<li id="ref-4"> Java Ultimate - <a href="http://javaultimate.com/java/collection_introduction.htm">Introduction to Collection Framework</a></li>
<li id="ref-5"> Oracle - <a href="https://docs.oracle.com/javase/8/docs/api/java/io/package-summary.html">java.io package summary</a></li>
<li id="ref-6"> StackOverflow - <a href="http://stackoverflow.com/questions/4716503/reading-a-plain-text-file-in-java">Reading a plain text file in Java</a></li>
<li id="ref-7"> Joel on Software - <a href="http://www.joelonsoftware.com/articles/Unicode.html">The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)</a></li>
<li id="ref-8"> StackOverflow - <a href="http://stackoverflow.com/questions/8978013/error-unmappable-character-for-encoding-utf8-during-maven-compilation#8979120">Error: unmappable character for encoding UTF8 during maven compilation</a></li>
<li id="ref-9"> Software Engineering - <a href="http://softwareengineering.stackexchange.com/a/300442/253793">Why is Akka good for concurrency?</a></li>
<li id="ref-10"> Oracle - <a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html">java.util.concurrent package summary</a></li>
<li id="ref-11"> StackOverflow - <a href="http://stackoverflow.com/questions/3295496/what-is-a-javabean-exactly">What is a JavaBean exactly?</a></li>
<li id="ref-12"> Eggbox Pod Repository - <a href="http://eggbox.fantomfactory.org/pods/afSizzle/api/SizzleDoc">Sizzle API</a></li>
<li id="ref-13"> Fantom-Factory - <a href="http://fanny.fantomfactory.org/">Fanny the Fantom Game</a></li>
</ol>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>10 Dec 2016</em> - Original article.</li>
</ul>
http://www.alienfactory.co.uk/articles/mime-types-for-web-fonts-in-bedsheet
MIME Types for Web Fonts in BedSheet
2013-12-21T00:00:00+01:00
2016-12-08T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAMAAAAL34HQAAAASFBMVEX///8AAADw8PAEBAQZGRnv7++fn58MDAx4eHghISH7+/v09PTS0tLp6enCwsJYWFguLi7d3d0/Pz8BAQGUlJRoaGiFhYWxsbF4/W5YAAACSElEQVR42uzX2Y6DMAxAUZuEhC2sQ/n/P52lIKtL6vgBI41yn1txiohdIJfL5XK5XO7fVp5U8VC71/wGfAGNYv6enVuG1eEldQyrx0vqGZbDS9oY1hdekmNYFV7SF8Ma8JIqhjUj09+ppjCS/cl7b8yCKQ0Mq8ZIy7i6PsBTBUYqgGqK0PWbuw2jwUgzw3r7xdFNdBkB67V2cm9/eM2w8CVzC0AJWLGmasHnPFApl/ErUEJWvNXgU7LdMzZwBgsKi499vs4mGidyFrXiQ5NgyDs4jwWVYClWjErAYlvTrzUwk1fGEszIW+qQN83ZrIDUmsrq4GwW3FK3z0ijAc5ngU/cPpaOhgZrpbuQxq9BgwWJS3GhY6jBomfZpuk7FRaNbwMfmnBvAR3WhEdJr2OrEouemjLlnm5KLForfcpKnLRYDvdcir3UYnV09BOGPGixAm0ffprOaixIGPPNwar0WCM/T9uDtWmx6JT5Js4yeC/osXqeVe6spdFjtQerhVhhIbkWqzH7UizYDWU1WX5nlexbogVFlt0fnMD+t64vYGFgd8+gyIKD1bEvbu4KVh//RP2XnTRZc32vAzZFFpVZaWUWCMosEJRZICizQFBmgaDMAkGZBYIy67t9OzYBEAgAGJhCxMLy+f1HdYIvAmLx5ibIAkEoC6EshLIQykIoC6EshLIQykIoC6EshLIQykIoC6EshLIQykIoC6EshLIQykIoC6EshLIQykLYKus+l8Yqa5xLN8J3Z/pVVlllbZQ1j1dNkiRJfuQBvIQkY4+6K/EAAAAASUVORK5CYII=" alt="Mime Type"> The <a href="http://css-tricks.com/snippets/css/using-font-face/">CSS @font-face rule</a> is a way to embed fonts in web pages.</p>
<p class="lead">The ever popular <a href="http://getbootstrap.com/">Bootstrap</a> uses web fonts to show <a href="http://getbootstrap.com/components/#glyphicons">Glyph Icons</a> and there's always <a href="http://fontawesome.io/">Font Awesome</a> should you need more.</p>
<p class="lead"><a href="http://www.google.com/fonts">Google Fonts</a> and <a href="http://www.myfonts.com/">MyFonts</a> also have 100s and 1000s of free and commercial fonts should you need them.</p>
<p class="lead">The problem is that IE and Chrome won't always display / use these fonts until they are served correctly from the web / application server.</p>
<p>Following an answer I recently posted on <a href="http://stackoverflow.com/questions/2871655/proper-mime-type-for-fonts/20723357#20723357">StackOverflow</a>, I'll explain how to correctly serve web fonts from <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a>; an application server for the <a href="http://fantom-lang.org/">Fantom JVM language</a>.</p>
<p>Web font files typically have the extensions <code>.eot, .otf, svg, .ttf</code> and <code>.woff</code>. For browsers to recognise them, they need to be served up with the correct <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type in the HTTP Header</a>. The value of the <code>Content-Type</code> field is a MIME type.</p>
<h2 id="mimeTypes">MIME Types :: The Official List</h2>
<p>Over the years, there has been a lot of confusion as what the actual MIME types for web fonts should be. Most have only been resolved recently (as late as March 2013) by the official internet authorities: <a href="http://www.iana.org/assignments/media-types/media-types.xhtml">Internet Assigned Numbers Authority (IANA)</a> and the <a href="http://www.w3.org/">W3C</a>.</p>
<p>With supporting links, here are the official MIME types for web fonts:</p>
<ul class="blendChildren">
<li><code>.eot</code> -> <a href="http://www.iana.org/assignments/media-types/application/vnd.ms-fontobject">application/vnd.ms-fontobject</a> (as from December 2005)</li>
<li><code>.otf</code> -> <a href="http://www.iana.org/assignments/media-types/application/font-sfnt">application/font-sfnt</a> (as from March 2013)</li>
<li><code>.svg</code> -> <a href="http://www.w3.org/TR/SVG/mimereg.html">image/svg+xml</a> (as from August 2011)</li>
<li><code>.ttf</code> -> <a href="http://www.iana.org/assignments/media-types/application/font-sfnt">application/font-sfnt</a> (as from March 2013)</li>
<li><code>.woff</code> -> <a href="http://www.iana.org/assignments/media-types/application/font-woff">application/font-woff</a> (as from January 2013)</li>
<li><code>.woff2</code> -> <a href="https://www.w3.org/TR/WOFF2/#IMT">font/woff2</a> (proposed by W3C in March 2016)</li>
</ul>
<p>Note there is a movement to change all the above to MIME types of <code>font/XXX</code>, as backed by the W3C in its proposal for WOFF v2. This is being tracked by the Internet Engineering Task Force (IETF) under <a href="https://datatracker.ietf.org/doc/draft-ietf-justfont-toplevel/">The font Top Level Type</a>, so it may all change yet!</p>
<p>It's worth mentioning that HTTP responses may <code>gzip</code> (or otherwise compress) all the above font formats except for <code>.woff</code> & <code>.woff2</code>, which are already heavily compressed.</p>
<h2 id="configureBedSheet">How to Configure BedSheet / Fantom</h2>
<p>When <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a> serves up web font files, it converts the file extension (.eot, .otf, ...) to a MIME Type. It does this using the Fantom's <a href="http://fantom.org/doc/sys/MimeType.html#forExt">MimeType.forExt()</a> method which, in turn, uses the file <code>%FAN_HOME%/etc/sys/ext2mime.props</code> as a lookup table. By default, Fantom does not have definitions in this file for web fonts, so we have to manually add them.</p>
<p>Open up <code>%FAN_HOME%/etc/sys/ext2mime.props</code> and add the following:</p>
<pre class="syntax microlight notForIe ">eot=application/vnd.ms-fontobject
otf=application/font-sfnt
ttf=application/font-sfnt
woff=application/font-woff
woff2=font/woff2
</pre></pre>
<p>Note that <code>SVG</code> is already defined so we do not add it. (Doing so would result in an error.)</p>
<p>Also note that <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a> is configured out of the box to <code>gzip</code> compress web fonts, so no extra work is required for that.</p>
<p>And that's it! Fantom and <a href="http://eggbox.fantomfactory.org/pods/afBedSheet/">BedSheet</a> are now configured to serve up web font files!</p>
<h2 id="configureHeroku">How to Configure Heroku</h2>
<p>Editing Fantom system files is fine on dev, or even a prod machine; but what do you do when deploying to a cloud platform such as <a href="https://www.heroku.com/">Heroku</a>? For each time your app is deployed, it downloads a fresh installation of Fantom!</p>
<p>Thankfully the <a href="https://bitbucket.org/AlienFactory/heroku-buildpack-fantom">Fantom Build Pack for Heroku</a> has a build hook where we can automate the MIME type addition. Add the following to your <code>build.fan</code>:</p>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe fantom">using build
class Build : BuildPod {
@Target { help = "Heroku pre-compile hook" }
Void herokuPreCompile() {
...
// patch web font mime types
patchMimeTypes([
"eot" : "application/vnd.ms-fontobject",
"otf" : "application/font-sfnt",
"svg" : "image/svg+xml",
"ttf" : "application/font-sfnt",
"woff" : "application/font-woff",
"woff2" : "font/woff2"
])
}
private Void patchMimeTypes(Str:Str extToMimeTypes) {
ext2mime := Env.cur.findFile(`etc/sys/ext2mime.props`)
mimeTypes := ext2mime.readProps
toAdd := extToMimeTypes.exclude |mime, ext->Bool| {
mimeTypes.containsKey(ext)
}
if (!toAdd.isEmpty) {
log.indent
exts := toAdd.keys.sort.join(", ")
log.info("Mime type for file extension(s) ${exts} do not exist")
log.info("Patching `${ext2mime.normalize}`...")
toAdd.keys.sort.each |ext| {
mimeTypes = mimeTypes.rw.add(ext, extToMimeTypes[ext])
log.info(" - ${ext} : " + mimeTypes[ext])
}
ext2mime.writeProps(mimeTypes)
log.info("Done.")
log.unindent
}
}
}
</pre></pre>
<p>When deployed to Heroku, your console should give the following message:</p>
<pre class="syntax microlight notForIe ">Mime type for file extension(s) eot, otf, ttf, woff do not exist
Patching `file:/C:/Apps/fantom-1.0.65/etc/sys/ext2mime.props`...
- eot : application/vnd.ms-fontobject
- otf : application/font-sfnt
- ttf : application/font-sfnt
- woff : application/font-woff
- woff2 : font/woff2
Done.
</pre></pre>
<p>Have fun!</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>8 Dec 2016</em> - Added WOFF v2 and the W3Cs proposal for a top level <code>font/</code> MIME type.</li>
</ul>
http://www.alienfactory.co.uk/articles/bnt02-bed-nap-in-f4
Bed Nap in F4
2014-08-27T00:00:00+02:00
2016-12-05T00:00:00+01:00
Steve Eynon
http://www.alienfactory.co.uk/contact
<p class="lead"><img class="img-fluid" src="/images/articles/f4.png" alt="F4 Logo"> I tend to do all my Fantom development in the <a href="http://www.xored.com/products/f4/">F4 IDE by Xored</a> so this article guides you through how to use it with the Bed Nap Tutorial application.</p>
<p>If you have not read it already, <a href="/articles/intro-to-f4">An Introduction to the F4 IDE</a> provides an excellent overview of the features of F4.</p>
<p>For reference this article was written with F4 Version: 1.1.0.</p>
<ul class="blendChildren">
<li><a href="#createTheF4Project">Create the F4 Project</a>
<ul class="blendChildren">
<li><a href="#theImportWizard">The Import Wizard</a></li>
</ul>
</li>
<li><a href="#runningBedNap">Running Bed Nap</a></li>
<li><a href="#bedNapRunConfiguration">Bed Nap Run Configuration</a></li>
<li><a href="#debuggingBedNap">Debugging Bed Nap</a></li>
<li><a href="#sourceCode">Source Code</a></li>
</ul>
<h2 id="createTheF4Project">Create the F4 Project</h2>
<p>An F4 project is defined by 3 configuration files in the root of a project directory, along side the <code>build.fan</code> file:</p>
<ul class="blendChildren">
<li><code>.buildpath</code></li>
<li><code>.classpath</code></li>
<li><code>.project</code></li>
</ul>
<p>(Yes, all the filenames start with a dot!)</p>
<p>There are 2 ways to create an F4 project and the assocaited config files:</p>
<ul class="blendChildren">
<li>Use the <code>New Project</code> wizard; tick some boxes, select some directories, and F4 will create the config files for you.</li>
<li>Or create and edit the 3 files by hand and use <code>Import... -> Existing Projects into Workspace</code> wizard.</li>
</ul>
<p>While the first option sounds easy I find it is fraught with errors, so I favour the later option instead.</p>
<h3 id="theImportWizard">The Import Wizard</h3>
<p>In the project directory create the following 3 files:</p>
<h4 id="fan.fandoc.Code@19ac0d08"><code>.buildpath</code></h4>
<pre class="syntax microlight notForIe xml"><?xml version="1.0" encoding="UTF-8"?>
<buildpath>
<buildpathentry kind="src" path="fan"/>
<buildpathentry kind="con" path="org.eclipse.dltk.launching.INTERPRETER_CONTAINER"/>
</buildpath>
</pre></pre>
<p>This adds <code>fan/</code> as a Fantom Source Folder.</p>
<h4 id="fan.fandoc.Code@176bf04d"><code>.classpath</code></h4>
<pre class="syntax microlight notForIe xml"><?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="com.xored.fanide.jdt.launching.FANJAVA_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>
</pre></pre>
<p><code>output</code> specifies where the <code>.pod</code> file will be built to.</p>
<h4 id="fan.fandoc.Code@13546678"><code>.project</code></h4>
<div class="selectCode">select all</div><pre class="syntax microlight notForIe xml"><?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Bed Nap Tutorial</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.dltk.core.scriptbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.xored.fanide.core.nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
</pre></pre>
<p>Note that each project in F4 needs a unique name, so when adding multiple projects you'll have to update the <code>name</code> element above.</p>
<p>Now go to <code>File -> Import... -> Existing Projects into Workspace</code>. In <code>Select root directory</code>, select where the project files are held. The <code>Bed Nap Tutorial</code> should then appear in the list of Projects:</p>
<p><img class="img-fluid" src="/images/articles/2014/bnt02-bed-nap-in-f4.importDialogue.png" alt="Import Projects Dialogue"></p>
<p>Click <code>Finish</code> and you should have a new Fantom project in F4!</p>
<h2 id="runningBedNap">Running Bed Nap</h2>
<p>Next we're going to run Bed Nap from F4. We can already run Bed Nap from the cmd line, but if we can run it from F4 then we have the option of <em>Debugging</em> - which can be quite useful.</p>
<p>In F4 it is easier to run a class in your project than it is to run the BedSheet pod passing in parameters. So we'll create a <code>Main</code> class with a <code>main()</code> method:</p>
<pre class="syntax microlight notForIe fantom">using afBedSheet
class Main {
Void main() {
BedSheetBuilder("bednap").startWisp(8069, true, "dev")
}
}
</pre></pre>
<p>This pretty much just mimics what we call from the cmd line.</p>
<p>To run, right click and select <code>Run As -> Fantom class</code> and you should see the standard BedSheet output in the Console View.</p>
<p><img class="img-fluid" src="/images/articles/2014/bnt02-bed-nap-in-f4.consoleView.png" alt="Console View with BedSheet output"></p>
<p>To stop the web app, simply click the <code>Terminate</code> button as shown.</p>
<h2 id="bedNapRunConfiguration">Bed Nap Run Configuration</h2>
<p>When you run a class in F4, it creates a <code>Run Configuration</code>. To see and edit the run configurations, click the <code>Run As</code> button in the toolbar, or right click a fantom file and go to <code>Run As -> Run Configurations...</code>.</p>
<p>To quickly access and run a recent configuration, click the down chevron next to the <code>Run As</code> button on the toolbar. (It looks like an arrow in a green circle.)</p>
<p>We are going to save the run configuration for Bed Nap as file in the project. This is so if anyone else checks out the project, they too will automatically have Bed Nap listed as favourite in the <code>Run As</code> / <code>Debug As</code> drop down list.</p>
<p>First create an <code>.f4/</code> directory to save it in. Then edit the run configuration.</p>
<p>We will change the name from <code>Main</code> to something more recognisable: <code>Bed Nap Web App</code>.</p>
<p>We will also change the default directory it is run from. This becomes <em>very important</em> later on when we introduce templates. Under the <code>Arguments</code> tab, select <code>Working Directory: Other</code>, then <code>Workspace...</code> and <code>Bed Nap Tutorial</code>. This should fill out the textbox with:</p>
<pre class="syntax microlight notForIe ">${workspace_loc:Bed Nap Tutorial}</pre></pre>
<p><img class="img-fluid" src="/images/articles/2014/bnt02-bed-nap-in-f4.runConfig.args.png" alt="Bed Nap Run Configuration - Arguments Tab"></p>
<p>Go to the <code>Common</code> tab and click <code>Shared File</code> and <code>Browse...</code> to the newly created <code>f4</code> directory. This is where the <code>.launch</code> file will be saved.</p>
<p>We'll also add it as a favourite in the <code>Run</code> and <code>Debug</code> menus. When finished, your dialogue should look like this:</p>
<p><img class="img-fluid" src="/images/articles/2014/bnt02-bed-nap-in-f4.runConfig.common.png" alt="Bed Nap Run Configuration - Arguments Tab"></p>
<p>Clicking <code>Apply</code> will save the configuration.</p>
<h2 id="debuggingBedNap">Debugging Bed Nap</h2>
<p>Just a note that when it comes to debugging a BedSheet application, make sure the application is run <em>without</em> the development <code>proxy</code>. That is, pass <code>false</code> to the <code>BedSheetBuilder</code> in <code>Main</code>:</p>
<pre class="syntax microlight notForIe fantom">BedSheetBuilder("bednap").startWisp(8069, false, "dev")
</pre></pre>
<p>If you don't then you will be debugging the BedSheet Proxy web app, not the <em>Bed Nap</em> web app. Which isn't very useful!</p>
<h2 id="sourceCode">Source Code</h2>
<p>All the source code for this tutorial is available on the <a href="https://bitbucket.org/fantomfactory/bed-nap-tutorial">Bed Nap Tutorial Bitbucket Repository</a>.</p>
<p>Code for this particular article is available on the <a href="https://bitbucket.org/fantomfactory/bed-nap-tutorial/src/tip/?at=02-Bed-Nap-in-F4">02-Bed-Nap-in-F4</a> branch.</p>
<p>Use the following commands to check the code out locally:</p>
<pre class="syntax microlight notForIe ">C:\> hg clone https://bitbucket.org/fantomfactory/bed-nap-tutorial
C:\> cd bed-nap-tutorial
C:\> hg update 02-Bed-Nap-in-F4</pre></pre>
<p>Don't forget, you can trial the finished tutorial application at <a href="http://bednap.fantomfactory.org/">Bed Nap</a>.</p>
<p>Have fun!</p>
<h2 id="edits">Edits</h2>
<ul class="blendChildren">
<li><em>5 Dec 2016</em> - Corrected <em>Debugging Bed Nap</em> section and updated article to reference F4 1.1.</li>
<li><em>2 Aug 2016</em> - Updated tutorial to use BedSheet 1.5 & IoC 3.0.</li>
<li><em>2 Aug 2015</em> - Updated tutorial to use BedSheet 1.4.</li>
<li><em>27 Aug 2014</em> - Original article.</li>
</ul>