Python WSGI with Node-Webkit

[Repost from the old blog, don’t know if it works anymore]

Let us combine the greatness of HTML/CSS/Javascript for user interface with Python the best way to build backend to create a desktop app.

The basic idea is to use create a python webserver process, wait for it to start and display your app. After your app closes it will close the webserver process.

The code you can see and download below has a few extra features:

  • Splash screen while python process starts
  • Waits till webserver process returns a result so we really know it has started
  • F5 – Refresh
  • F11 – Fullscreen toggle
  • F12 – Show Dev Tools

To make this work use the zip file at the bottom and put it in same folder as NWJS.

Only tested on windows with node-webkit 0.9.2(0.11.9)

A pretty standard node-webkit package file:

{
  "name": "wsgi",
  "main": "app://whatever/start.html",
  "window": {
  	"width": 320,
  	"height": 240,  	
    "toolbar": false,
    "fullscreen": false
  }
}

The simple python WSGI app:

from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server

def simple_app(environ, start_response):
    setup_testing_defaults(environ)

    status = '200 OK'
    headers = [('Content-type', 'text/html')]

    start_response(status, headers)
    
    yield "<h1>Python WSGI App</h1>"

    yield "<pre>"
    for key, value in environ.iteritems():
        yield "%s: %s\n" % (key, value)


if __name__ == '__main__':    
    port = 44408
    httpd = make_server('', port, simple_app)
    print "Serving on port "+str(port)+"..."
    httpd.serve_forever()

Where the magic happens:

<!DOCTYPE html>
<html>
 <head>
 <title>Start</title>
 <script type="text/javascript">
 var platform = require("os").platform; 
 </script>
 <style>
 body {
 background: #272822;
 color: #FFF;
 font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif;
 cursor: default;
 } 
 </style>
 </head>
 <body>
 <h1>Loading...</h1>
 We are using node.js <script>document.write(process.version)</script>.<br/>
 platform: <script>document.write(platform())</script>.<br/>
 status: <span id="status">status</span>
 
 <script type="text/javascript">
//************************************************************ 
var pyexec = 'C:/Python27/python.exe';
var basedir = '.'; //working dir
var pywsgi = basedir+'/wsgi_app.py';
 
var startpage = 'http://127.0.0.1:44408/';
var pagecheck = 'http://127.0.0.1:44408/404'; //no side-effect page to check if webserver is running
//************************************************************ 
var wsgi_process = null;
var spawn = require("child_process").spawn;
//------------------------------------------------------------
function StartWSGI()
{
 console.log('StartWSGI');

if (platform() == 'win32')
 {
 pyexec = pyexec.replace('/','\\');
 pywsgi = pywsgi.replace('/','\\');
 basedir = basedir.replace('/','\\');
 }

child = spawn(pyexec, [pywsgi,],{cwd:basedir});
 child.stdout.on("data", function(data) {
 return console.log("stdout:" + data);
 });
 child.stderr.on("data", function(data) {
 return console.log("ERROR: " + data);
 });

child.on("exit", function(code) {
 return console.log("Encoding process exited with code: " + code);
 });

console.log("StartWSGI_End");

wsgi_process = child;

function transferComplete(evt) {
 InitMainWindow();
 }

function transferFailed(evt) {
 document.getElementById("status").textContent = "Waiting...";
 checkServer();
 }

function reqListener () {
 document.getElementById("status").textContent = "Launching...";
 };

function checkServer()
 {
 var oReq = new XMLHttpRequest(); 
 oReq.addEventListener("load", transferComplete, false);
 oReq.addEventListener("error", transferFailed, false);

oReq.onload = reqListener;
 oReq.open("GET", pagecheck, true);
 oReq.send();
 }

document.getElementById("status").textContent = "checking...";
 checkServer();

}
//------------------------------------------------------------
function InitMainWindow()
{
 var gui = require('nw.gui');

// Get the current window
 var win = gui.Window.get();

var new_win = 
 gui.Window.open(startpage, {
 position: 'center',
 width: 640,
 height: 480,
 "toolbar": false,
 focus: true,
 show: false
 });

// And listen to new window's focus event
 new_win.on('focus', function() {
 console.log('New window is focused-b');
 });

// Release the 'win' object here after the new window is closed.
 new_win.on('closed', function() {
 console.log("mainclose");
 wsgi_process.kill();

win.close();
 win = null;

});

new_win.on('loaded', function() {
 console.log('New window is loaded');
 win.hide(); 
 new_win.show(); 
 new_win.focus();

var awin = gui.Window.get(this);
 awin = this.window;
 awin.onkeyup = function(e) {
 //console.log(e);
 if (e.keyIdentifier == "F5")
 {
 new_win.reload();
 }
 if (e.keyIdentifier == "F11")
 {
 new_win.toggleFullscreen();
 }
 if (e.keyIdentifier == "F12")
 {

if (new_win.isDevToolsOpen())
 {
 new_win.closeDevTools();
 }else{
 new_win.showDevTools();
 }

} 
 }; 
 });

}
//======================================================================
var gui = require('nw.gui');
var win = gui.Window.get();

win.on('loaded', function() {
 console.log('New window is loaded');
 StartWSGI();
 
 });
//======================================================================
 </script>
 </body>
</html>

Download code (Zip)