After exploring Java integration using the new Native Process API (here and here), Excel integration using the new file.openWithDefaultApplication(), and the new Microphone API, here is another application I wrote, this time to explore the new Adobe AIR 2.0 Server Socket API.
The “Mini AIR Web Server” is a simplistic implementation of an HTTPServer. Needless to say that it is far from being a production quality Web Server. The goal here is simply to use the well understood mechanics of a web server to learn how to use server sockets in AIR 2.0.
Installation Instructions
- Download the AIR 2.0 beta runtime here.
- Download MiniAIRWebServer.air here.
- Double-click MiniAIRWebServer.air in Explorer or Finder to start the installation process.
To test the application, open a browser and test the sample HTML pages provided with the application:
NOTE: You may have to change the port number in these URLs depending on the port number you enter in the application.
You can add your own content in the webroot folder in the applicationStorageDirectory.
View Source is enabled, but if you just want to take a quick peak at the code, here is MiniAIRWebServer.mxml:
<?xml version="1.0" encoding="utf-8"?>
<!-- Author: Christophe Coenraets http://coenraets.org -->
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"
title="Mini AIR Web Server"
applicationComplete="init()" viewSourceURL="srcview/index.html">
<s:layout>
<s:VerticalLayout paddingTop="8" paddingLeft="8" paddingRight="8" paddingBottom="8"/>
</s:layout>
<fx:Script>
<![CDATA[
import flash.events.Event;
import flash.events.ProgressEvent;
import flash.events.ServerSocketConnectEvent;
import flash.net.ServerSocket;
import flash.net.Socket;
import flash.utils.ByteArray;
import mx.controls.Alert;
private var serverSocket:ServerSocket;
private var mimeTypes:Object = new Object();
private function init():void
{
// The mime types supported by this mini web server
mimeTypes[".css"] = "text/css";
mimeTypes[".gif"] = "image/gif";
mimeTypes[".htm"] = "text/html";
mimeTypes[".html"] = "text/html";
mimeTypes[".ico"] = "image/x-icon";
mimeTypes[".jpg"] = "image/jpeg";
mimeTypes[".js"] = "application/x-javascript";
mimeTypes[".png"] = "image/png";
// Initialize the web server directory (in applicationStorageDirectory) with sample files
var webroot:File = File.applicationStorageDirectory.resolvePath("webroot");
if (!webroot.exists)
{
File.applicationDirectory.resolvePath("webroot").copyTo(webroot);
}
}
private function listen():void
{
try
{
serverSocket = new ServerSocket();
serverSocket.addEventListener(Event.CONNECT, socketConnectHandler);
serverSocket.bind(Number(port.text));
serverSocket.listen();
log.text += "Listening on port " + port.text + "...\n";
}
catch (error:Error)
{
Alert.show("Port " + port.text +
" may be in use. Enter another port number and try again.\n(" +
error.message +")", "Error");
}
}
private function socketConnectHandler(event:ServerSocketConnectEvent):void
{
var socket:Socket = event.socket;
socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
}
private function socketDataHandler(event:ProgressEvent):void
{
try
{
var socket:Socket = event.target as Socket;
var bytes:ByteArray = new ByteArray();
socket.readBytes(bytes);
var request:String = "" + bytes;
log.text += request;
var filePath:String = request.substring(4, request.indexOf("HTTP/") - 1);
var file:File = File.applicationStorageDirectory.resolvePath("webroot" + filePath);
if (file.exists && !file.isDirectory)
{
var stream:FileStream = new FileStream();
stream.open( file, FileMode.READ );
var content:ByteArray = new ByteArray();
stream.readBytes(content);
stream.close();
socket.writeUTFBytes("HTTP/1.1 200 OK\n");
socket.writeUTFBytes("Content-Type: " + getMimeType(filePath) + "\n\n");
socket.writeBytes(content);
}
else
{
socket.writeUTFBytes("HTTP/1.1 404 Not Found\n");
socket.writeUTFBytes("Content-Type: text/html\n\n");
socket.writeUTFBytes("<html><body><h2>Page Not Found</h2></body></html>");
}
socket.flush();
socket.close();
}
catch (error:Error)
{
Alert.show(error.message, "Error");
}
}
private function getMimeType(path:String):String
{
var mimeType:String;
var index:int = path.lastIndexOf(".");
if (index > -1)
{
mimeType = mimeTypes[path.substring(index)];
}
return mimeType == null ? "text/html" : mimeType; // default to text/html for unknown mime types
}
]]>
</fx:Script>
<s:HGroup verticalAlign="middle">
<s:Label text="Port:"/>
<s:TextInput id="port" text="8888" width="50"/>
<s:Button label="Listen" click="listen()"/>
</s:HGroup>
<s:TextArea id="log" width="100%" height="100%" />
</s:WindowedApplication>
Holy shit very very nice… Thx for this little snippet!
This is awesome, I just love it!
nice solution.
But the pink code hurt my eyes!!
Awesome! Now if there was a way for AIR to know when a file changes (I guess it could just poll) then an AIR app could be built for providing a live preview when developing in HTML/CSS/Javascript. Given a folder and HTML file, it could serve the page etc with the above, show the page with the AIR browser, and as soon as a file changes in the folder, refresh the browser!
Just Great..
I produced “airhttpd” is a HTTP server by AIR 2.0 too.
Great Work Christophe. However, this comment is mostly a request. Since, you work at Adobe, I would like you to please convince them to create a robust FTP class in AIR2 so that we don’t have to fiddle with sockets. I am an AIR+AJAX developer and sockets are a pain. I am not able to successfully implement an FTP functionality in my AIR app as I have to go through the sockets in detail. It would be a great help to all.
Thanks, Gaurav
Thank for the snippets!
Christophe,
When I try to compile your source in Flash Builder Beta 2, I get a unspecified error at this socket function.
private function socketConnectHandler(event:ServerSocketConnectEvent):void
I would appreciate any insight you might have.
Are you able to publish your working project file so that we could start from a known working point.
Outstanding tutorial, thanks for the fantastic work.
Cheers,
James
@James,
Yeah, same problem with me too… the problem could be found in line 65:
private function socketConnectHandler(event:ServerSocketConnectEvent):void
{
var socket:Socket = event.socket;
socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
}
with the following error message:
“Type was not found or was not a compile-time constant: ServerSocketConnectEvent.”
This would be very useful if you can provide us with a solution for this, so we can compile it in our end.
Thanks a lot Christophe for another great AIR application!
Nathan
Any performance improvements in the overall socket library with 2.0?
With the 1.5 runtime, socket sends of large buffers (1MB) seems to be limited to 100KBytes/second regardless of what the link and endpoint can do. Equivalent code in other interpreted run time environments can go 10-15 times faster.
thank you so much for showing the source code
Christophe,
This is pretty awesome! If AS3 had some sort of dynamic AS3 evaluation, someone could write a web server which could execute server side AS3! There are a libraries out there, but it would be pretty slow, since they interpret/run the script.
I hope that AIR gets some scripting engine support in the future, with JIT compilation, etc!
- Derrick
@James, @Nathan
Same problem with me too
private function socketConnectHandler(event:ServerSocketConnectEvent):void
{
var socket:Socket = event.socket;
socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
}
Any body got the solutions?
Christophe Could you provide me the published project code?
Thanks in advance
I have solved the problem:
“Type was not found or was not a compile-time constant: ServerSocketConnectEvent.”
I download the AIR 2.0 SDK
and follow this document:
http://labs.adobe.com/wiki/index.php/AIR_2:Release_Notes#How_to_overlay_the_Adobe_AIR_SDK_for_use_with_the_Flex_SDK
It is work.
(sorry, my english is not good, if you dont know what I say, I’m so sorry….)
Solution
Use AIR2Beta sdk. Air2Beat2 sdk doesnt work.
To solve the issue (ServerSocketConnectEvent )
use Air2Beta1 instead of Air2Beat2.
Congratulations for the excellent sample =)
hi, i have problem with this code..
it show error wtih this line -> private function onConnect(e:ServerSocketConnectEvent):void
Hi Christopher, I used your inspiring tutorial to create a mp3 server, which serves songs to Android, and it’s working amazingly well on Windows 7, Linux and OSX machines.
However Windows XP and Vista users are experiencing a very strange error, all the mp3′s served are cut off after only a few seconds of playing,, and I receive a SoundChannel.COMPLETE event.
This has nothing to do with the client, as I can load the mp3 file from the server, in my web browser and observe exactly the same thing?
I am passing the proper mime-type, and content-length headers.
I also have issue sending a compressed byteArray over the socket, it can never be decompressed client side, however I’ve verified the dat file on the server is perfectly fine, so something is getting screwed up during transmition. Again, very small subset of users are experiencing this…
Any idea? I’m going crazy over here…
Hi Chris,
Thanks for posting this. I have been using Serial servers for years to create a connection between apps and Air. This little web server was enough to get me working on a full refactoring and removing of external JAVA TCP/IP Clients.
Keep on blogging!
Ben
Just a quick follow up on my issues, it was all caused by calling socket.close() after my flush(). (I don’t know why I was doing this in the first place)
In Win7 and OSX this works fine, but on XP/Vista this will kill the urlStream, It’s almost as though flush() is async in XP/Vista and syncronous in the others…?
Either way, just leave the socket open and it works great!
This is a wonderful example. However, I’m trying to develop peer to peer games for mobile devices that can communicate with each other directly on a network (with their local IP block) for WiFi games.
ServerSocket is not available for mobile device AIR.
I am not finding any alternatives that will allow one Android pad or iPad to be the game host and allow others to connect into the game for multiplayer fun.
This is amazing! Works great.
I am wondering if could work for Air mobile in iPhone and Android??
Is that posible??
It’s just amazing just what I need. But I am wondering how many user can be supported at once. Is there a number??