Skip to main content

Skitai WSGI App Engine

Project description

Copyright (c) 2015 by Hans Roh

License: BSD

Introduce

Skitai App Engine (SAE) is a kind of branch of Medusa Web Server - A High-Performance Internet Server Architecture.

Medusa is different from most other servers because it runs as a single process, multiplexing I/O with its various client and server connections within a single process/thread.

SAE orients light-weight, simplicity and strengthen networking operations with external resources - HTTP / HTTPS / XML-RPC / PostgreSQL - keeping very low costs.

It is influenced by Zope and Flask a lot.

  • SAE can be run as Web, XML-RPC and Reverse Proxy Loadbancing Server

  • SAE can handle massive RESTful API/RPC/HTTP(S) connections based on asynchronous socket framework at your apps easily

  • SAE provides asynchronous connection to PostgreSQL

Skitai is not a framework for convinient developing, module reusability and plugin flexibility etc. It just provides some powerful communicating services for your WSGI apps as both server and client.

From version 0.10, Skitai App Engine follows WSGI specification. So existing Skitai apps need to lots of modifications.

Conceptually, SAE has been seperated into two components:

  1. Skitai App Engine Server, for WSGI apps

  2. Saddle, the small WSGI middleware integrated with SAE. But you can also mount any WSGI apps and frameworks like Flask.

Mounting WSGI Apps

Here’s three WSGI app samples:

WSGI App at /var/wsgi/wsgiapp.py

def app (env, start_response):
  start_response ("200 OK", [("Content-Type", "text/plain")])
  return ['Hello World']

Flask App at /var/wsgi/flaskapp.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def index ():
  return "Hello World"

Skitai-Saddle App at /var/wsgi/skitaiapp.py

from skitai.saddle import Saddle
app = Saddle (__name__)

@app.route('/')
def index (was):
  return "Hello World"

For mounting to SAE, modify config file in /etc/skitaid/servers-enabled/sample.conf

[routes:line]

; for files like images, css
/ = /var/wsgi/static

; app mount syntax is path/module:callable
/ = /var/wsgi/wsgiapp:app
/aboutus = /var/wsgi/flaskapp:app
/services = /var/wsgi/skitaiapp:app

You can access Flask app from http://127.0.0.1:5000/aboutus and other apps are same.

Note: Mount point & App routing

If app is mounted to ‘/flaskapp’,

from flask import Flask
app = Flask (__name__)

@app.route ("/hello")
def hello ():
  return "Hello"

Above /hello can called, http://127.0.0.1:5000/flaskapp/hello

Also app should can handle mount point. In case Flask, it seems ‘url_for’ generate url by joining with env[“SCRIPT_NAME”] and route point, so it’s not problem. Skitai-Saddle can handle obiously. But I don’t know other WSGI middle wares will work properly.

Virtual Hosting

[routes:line]

: default
/ = /home/user/www

; exactly matching host
: www.mydomain.com mydomain.com
/ = home/user/mydomain.www

; matched *.mydomain.com
: .mydomain.com
/ = home/user/mydomain.any

New in version 0.10.5

Note: For virtual hosting using multiple Skitai instances with Nginx/Squid, see Virtual Hosting with Nginx / Squid at end of this document.

Note For Python 3 Users

Posix

SAE will be executed with /usr/bin/python (mostly symbolic link for /usr/bin/python2).

For using Python 3.x, change skitaid scripts’ - /usr/local/bin/sktaid*.py - first line from #!/usr/bin/python to #!/usr/bin/python3. Once you change, it will be kept, even upgrade or re-install skitai.

In this case, you should re-install skitai and requirements using ‘pip3 install …’.

Win32

Change python key value to like c:python34python.exe in c:skitaidetcskitaid.conf.

Asynchronous Requests Using Skitai WAS

‘WAS’ means (Skitai) WSGI Application Service.

Simple HTTP Request

Flask Style:

from flask import Flask, request
from skitai import was

app = Flask (__name__)
@app.route ("/get")
def get ():
  url = request.args.get('url', 'http://www.python.org')
  s = was.get (url)
  result = s.getwait (5) # timeout
  return result.data

Skitai-Saddle Style

from skitai.saddle import Saddle
app = Saddle (__name__)

@app.route ("/get")
def get (was, url = "http://www.python.org"):
  s = was.get (url)
  result = s.getwait (5) # timeout
  return result.data

Both can access to http://127.0.0.1:5000/get?url=https%3A//pypi.python.org/pypi .

If you are familar to Flask then use it, otherwise choose any WSGI middle ware you like include Skitai-Saddle.

Also note that if you want to use WAS services in your WSGI middle wares except Skitai-Saddle, you should import was.

from skitai import was

Here’re post, file upload method examples:

s1 = was.post (url, {"user": "Hans Roh", "comment": "Hello"})
s2 = was.upload (url, {"user": "Hans Roh", "file": open (r"logo.png", "rb")})

result = s1.getwait (5)
result = s2.getwait (5)

Here’s XMLRPC request for example:

s = was.rpc (url)
s.get_prime_number_gt (10000)
result = s.getwait (5)

Avaliable methods are:

  • was.get (url, auth = (username, password))

  • was.post (url, data = data, auth = (username, password))

  • was.rpc (url, auth = (username, password)) # XMLRPC

  • was.ws (url, data, auth = (username, password)) # Web Socket

  • was.put (url, data = data, auth = (username, password))

  • was.delete (url)

  • was.upload (url, data, auth = (username, password)) # For clarity to multipart POST

Load-Balancing

If server members are pre defined, skitai choose one automatically per each request supporting fail-over.

At first, let’s add mysearch members to config file (ex. /etc/skitaid/servers-enabled/sample.conf),

[@mysearch]
ssl = yes
members = search1.mayserver.com:443, search2.mayserver.com:443

Then let’s request XMLRPC result to one of mysearch members.

@app.route ("/search")
def search (was, keyword = "Mozart"):
  s = was.rpc.lb ("@mysearch/rpc2")
  s.search (keyword)
  results = s.getwait (5)
  return result.data

It just small change from was.rpc () to was.rpc.lb ()

Avaliable methods are:

  • was.get.lb ()

  • was.post.lb ()

  • was.rpc.lb ()

  • was.ws.lb ()

  • was.upload.lb ()

  • was.put.lb ()

  • was.delete.lb ()

Note: If @mysearch member is only one, was.get.lb (“@mydb”) is equal to was.get (“@mydb”).

Note2: You can mount cluster @mysearch to specific path as proxypass like this:

At config file

[routes:line]
; for files like images, css
/ = /var/wsgi/static

; app mount syntax is path/module:callable
/search = @mysearch

It can be accessed from http://127.0.0.1:5000/search, and handled as load-balanced proxypass.

Map-Reducing

Basically same with load_balancing except SAE requests to all members per each request.

@app.route ("/search")
def search (was, keyword = "Mozart"):
  s = was.rpc.map ("@mysearch/rpc2")
  s.search (keyword)
  results = s.getswait (2)

  all_results = []
  for result in results:
     all_results.extend (result.data)
  return all_results

There are 2 changes:

  1. from was.rpc.lb () to was.rpc.map ()

  2. form s.getwait () to s.getswait () for multiple results

Avaliable methods are:

  • was.get.map ()

  • was.post.map ()

  • was.rpc.map ()

  • was.ws.map ()

  • was.upload.map ()

  • was.put.map ()

  • was.delete.map ()

PostgreSQL Map-Reducing

This sample is to show querying sharded database. Add mydb members to config file.

[@mydb]
type = postresql
members = s1.yourserver.com:5432/mydb/user/passwd, s2.yourserver.com:5432/mydb/user/passwd
@app.route ("/query")
def query (was, keyword):
  s = was.db.map ("@mydb")
  s.execute("SELECT * FROM CITIES;")

  results = s.getswait (timeout = 2)
  all_results = []
  for result in results:
    if result.status == 3:
      all_results.append (result.data)
  return all_results

Basically same usage concept with above HTTP Requests.

Avaliable methods are:

  • was.db (“127.0.0.1:5432”, “mydb”, “postgres”, “password”)

  • was.db (“@mydb”)

  • was.db.lb (“@mydb”)

  • was.db.map (“@mydb”)

Note: if @mydb member is only one, was.db.lb (“@mydb”) is equal to was.db (“@mydb”).

Sending e-Mails

# email delivery service
e = was.email (subject, snd, rcpt)
e.set_smtp ("127.0.0.1:465", "username", "password", ssl = True)
e.add_text ("Hello World<div><img src='cid:ID_A'></div>", "text/html")
e.add_attachment (r"001.png", cid="ID_A")
e.send ()

With asynchronous email delivery service, can add default SMTP Server config to skitaid.conf (/etc/skitaid/skitaid.conf or c:skitaidetcskitaid.conf). If it is configured, you can skip e.set_smtp(). But be careful for keeping your smtp password.

[smtpda]
smtpserver = 127.0.0.1:25
user =
password =
ssl = no
max_retry = 10
undelivers_keep_max_days = 30

Other Utility Service

  • was.status ()

  • was.tojson ()

  • was.fromjson ()

  • was.toxml () # XMLRPC

  • was.fromxml () # XMLRPC

HTML5 Web Socket

New in version 0.11

There’re 3 Skitai ‘was’ client-side web socket services:

  • was.ws ()

  • was.ws.lb ()

  • was.ws.map ()

It is desinged as simple & no stateless request-response model using web socket message frame for light overheaded server-to-server communication. For example, if your web server queries to so many other search servers via RESTful access, web socket might be a good alterative option. Think HTTP-Headerless JSON messaging. Usage is very simailar with HTTP request.

@app.route ("/query")
def query (was):
  s = was.ws (
      "ws://192.168.1.100:5000/websocket/echo",
      json.dumps ({"keyword": "snowboard binding"})
  )
  rs = s.getwait ()
  result = json.loads (rs.data)

Obiously, target server should have Web Socket app, routed to ‘/websocket/echo’ in this case.

Also at server-side, HTML5 Web Socket has been implemented.

But I’m not sure my implemetation is right way, so it is experimental and unstatable.

I think there’re 3 handling ways to use websockets.

  1. thread pool manages n websocket connection

  2. one thread per websocket connection

  3. one thread manages n websockets connection

So skitai supports above all 3 ways.

First of all, see conceptual client side java script for websocket.

<script language="javascript" type="text/javascript">
var wsUri = "ws://localhost:5000/websocket/chat";
testWebSocket();

function testWebSocket()
{
  websocket = new WebSocket(wsUri);
  websocket.onopen = function(evt) { onOpen(evt) };
  websocket.onclose = function(evt) { onClose(evt) };
  websocket.onmessage = function(evt) { onMessage(evt) };
  websocket.onerror = function(evt) { onError(evt) };
}

function onOpen(evt) {doSend("Hello");}
function onClose(evt) {writeToScreen("DISCONNECTED");}
function onMessage(evt) {writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');}
function onError(evt) {writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);}
function doClose () {websocket.close();}
function doSend(message) {websocket.send(message);}
</script>

If your WSGI app enable handle websocket, it should give initial parameters to Skitai.

You should check exist of env [“websocket_init”], set initializing parameters.

initializing parameters should be tuple of (websocket design spec, keep alive timeout, variable name)

websocket design specs can be choosen one of 3 .

WEBSOCKET_REQDATA

  • Thread pool manages n websocket connection

  • It’s simple request and response way like AJAX

  • Use skitai initail thread pool, no additional thread created

  • Low cost on threads resources, but reposne cost is relatvley high than the others

WEBSOCKET_DEDICATE

  • One thread per websocket connection

  • Use when reponse maiking is heavy and takes long time

  • New thread created per websocket connection

WEBSOCKET_MULTICAST

  • One thread manages n websockets connection

  • Chat room model, all websockets will be managed by single thread

  • New thread created per chat room

keep alive timeout is seconds.

variable name is various usage per each design spec.

WEBSOCKET_REQDATA

Here’s a echo app for showing simple request-respone.

Client can connect by ws://localhost:5000/websocket/chat.

Skitai-Saddle Style

from skitai.saddle import Saddle
import skitai

app = Saddle (__name__)
app.debug = True
app.use_reloader = True

@app.route ("/websocket/echo")
def echo (was, message = ""):
  if "websocket_init" in was.env:
    was.env ["websocket_init"] = (skitai.WEBSOCKET_REQDATA, 60, "message")
    return ""
  return "ECHO:" + message

Flask Style

from flask import Flask, request
import skitai

app = Flask (__name__)
app.debug = True
app.use_reloader = True

@app.route ("/websocket/echo")
def echo ():
  if "websocket_init" in request.environ:
    request.environ ["websocket_init"] = (skitai.WEBSOCKET_REQDATA, 60, "message")
    return ""
  return "ECHO:" + request.args.get ("message")

In this case, variable name is “message”, It means take websocket’s message as “message” arg.

WEBSOCKET_DEDICATE

This app will handle only one websocket client. and if new websocekt connected, will be created new thread.

Client can connect by ws://localhost:5000/websocket/talk?name=Member.

@app.route ("/websocket/talk")
def talk (was, name):
  if "websocket_init" in was.env:
    was.env ["websocket_init"] = (skitai.WEBSOCKET_DEDICATE, 60, None)
    return ""

  ws = was.env ["websocket"]
  while 1:
    messages = ws.getswait (10)
    if messages is None:
      break
    for m in messages:
      if m.lower () == "bye":
        ws.send ("Bye, have a nice day." + m)
        ws.close ()
        break
      elif m.lower () == "hello":
        ws.send ("Hello, " + name)
      else:
        ws.send ("You Said:" + m)

In this case, variable name should be None. If exists, will be ignored.

WEBSOCKET_MULTICAST

Here’s simple mutiuser chatting app.

Many clients can connect by ws://localhost:5000/websocket/chat?roomid=1. and can chat between all clients.

@app.route ("/websocket/chat")
def chat (was, roomid):
  if "websocket_init" in was.env:
    was.env ["websocket_init"] = (skitai.WEBSOCKET_MULTICAST, 60, "roomid")
    return ""

  ws = was.env ["websocket"]
  while 1:
    messages = ws.getswait (10)
    if messages is None:
      break
    for client_id, m in messages:
      ws.sendall ("Client %d Said: %s" % (client_id, m))

In this case, variable name is “roomid”, then Skitai will create websocket group seperatly.

You can access all examples by skitai sample app after installing skitai.

sudo skitaid-instance.py -v -f sample

Then goto http://localhost:5000/websocket in your browser.

Request Handling with Saddle

Saddle is WSGI middle ware integrated with Skitai App Engine.

Flask and other WSGI middle ware have their own way to handle request. So If you choose them, see their documentation. And note that below objects will NOT be avaliable on other WSGI middle wares.

Debugging

app = Saddle (__name__)
app.debug = True # output exception information
app.use_reloader = True # auto realod on file changed

For output message & error in console:

Posix

sudo /usr/local/bin/skitai-instance.py -v -f sample

Win32

c:skitaidbinskitai-instance.py -v -f sample

Access Request

was.request.get_header ("content-type") # case insensitive
was.request.get_header () # retrun header all list
was.request.command # lower case get, post, put, ...
was.request.version # HTTP Version, 1.0, 1.1
was.request.uri
was.request.get_body ()
was.request.get_remote_addr ()
was.request.get_user_agent ()

Handle Response

was.response ["Content-Type"] = "text/plain"
was.response.set_status ("200 OK") # default value
return "Hello"

was.response.send_error ("500 Server Error", why = "It's not my fault")
return "" # should null string/bytes after call send_error ()

was.response ["Content-Type"] = "video/mp4"
return open ("mypicnic.mp4", "rb")

was.response ["Content-Type"] = "text/csv"
def generate():
  for row in iter_all_rows():
    yield ','.join(row) + '\n'
return generate()

Available return types are:

  • String, Bytes, Unicode

  • File-like object has ‘read (buffer_size)’ method, optional ‘close ()’

  • Iterator/Generator object has ‘next() or _next()’ method, optional ‘close ()’ and shoud raise StopIteration if no more data exists.

  • Something object has ‘more()’ method, optional ‘close ()’

  • Classes of skitai.lib.producers

  • List/Tuple contains above objects

  • XMLRPC dumpable object for if you want to response to XMLRPC

The object has ‘close ()’ method, will be called when all data consumed, or socket is disconnected with client by any reasons.

Getting URL Parameters

@app.route ("/hello")
def hello_world (was, num = 8):
  return num
# http://127.0.0.1:5000/hello?num=100


@app.route ("/hello/<int:num>")
def hello_world (was, num = 8):
  return str (num)
  # http://127.0.0.1:5000/hello/100

Available fancy URL param types:

  • int

  • float

  • path: /download/<int:major_ver>/<path>, should be positioned at last like /download/1/version/1.1/win32

  • If not provided, assume as string. and all space char replaced to “_’

Getting Form Parameters

@app.route ("/hello")
def hello (was, **form):
      return "Post %s %s" % (form.get ("userid", ""), form.get ("comment", ""))

@app.route ("/hello")
def hello_world (was, userid, **form):
      return "Post %s %s" % (userid, form.get ("comment", ""))

File Upload

FORM = """
  <form enctype="multipart/form-data" method="post">
  <input type="hidden" name="submit-hidden" value="Genious">
  <p></p>What is your name? <input type="text" name="submit-name" value="Hans Roh"></p>
  <p></p>What files are you sending? <br />
  <input type="file" name="file">
  </p>
  <input type="submit" value="Send">
  <input type="reset">
</form>
"""

@app.route ("/upload")
def upload (was, *form):
  if was.request.command == "get":
    return FORM
  else:
    file = form.get ("file")
    if file:
      file.save ("d:\\var\\upload", dup = "o") # overwrite

file object has these attributes:

  • file.file: temporary saved file full path

  • file.name: original file name posted

  • file.size

  • file.mimetype

  • file.remove ()

  • file.save (into, name = None, mkdir = False, dup = “u”) * if name is None, used file.name * dup:

    • u - make unique (default)

    • o - overwrite

Access Environment Variables

was.env.keys ()
was.env.get ("CONTENT_TYPE")

Access App & Jinja Templates

if was.app.debug:
  was.app.get_template ("index-debug.html") # getting Jinja template
else:
  was.app.get_template ("index.html") # getting Jinja template

Directory structure sould be:

  • app.py

  • templates/index.html

Access Cookie

if was.cookie.get ("user_id") is None:
      was.cookie.set ("user_id", "hansroh")
  • was.cookie.set (key, val)

  • was.cookie.get (key)

  • was.cookie.remove (key)

  • was.cookie.clear ()

  • was.cookie.kyes ()

  • was.cookie.values ()

  • was.cookie.items ()

Access Session

To enable session for app, random string formatted securekey should be set for encrypt/decrypt session values.

WARN: securekey should be same on all skitai apps at least within a virtual hosing group, Otherwise it will be serious disater.

app.securekey = "ds8fdsflksdjf9879dsf;?<>Asda"
app.session_timeout = 1200 # sec

@app.route ("/session")
def hello_world (was, **form):
  if was.session.get ("login") is None:
    was.session.set ("user_id", form.get ("hansroh"))
  • was.session.set (key, val)

  • was.session.get (key)

  • was.session.remove (key)

  • was.session.clear ()

  • was.session.kyes ()

  • was.session.values ()

  • was.session.items ()

Building URL

@app.route ("/add")
def add (was, num1, num2):
  return int (num1) + int (num2)

was.app.build_url ("add", 10, 40) # returned '/add?num1=10&num2=40'
# BUT it's too long to use practically,
# was.ab is acronym for was.app.build_url
was.ab ("add", 10, 40) # returned '/add?num1=10&num2=40'
was.ab ("add", 10, num2=60) # returned '/add?num1=10&num2=60'

@app.route ("/hello/<name>")
def hello (was, name = "Hans Roh"):
  return "Hello, %s" % name

was.ab ("hello", "Your Name") # returned '/hello/Your_Name'

Chained Execution

@app.before_request
def before_request (was):
  if not login ():
    return "Not Authorized"

@app.after_request
def after_request (was):
  was.temp.user_id
  was.temp.user_status
  ...

@app.failed_request
def failed_request (was):
  was.temp.user_id
  was.temp.user_status
  ...

@app.teardown_request
def teardown_request (was):
  was.temp.resouce.close ()
  ...

@app.route ("/view-account")
def view_account (was, userid):
  was.temp.user_id = "jerry"
  was.temp.user_status = "active"
  was.temp.resouce = open ()
  return ...

For this situation, ‘was’ provide was.temp that is empty class instance. was.temp is valid only in cuurent request. After end of current request, was.temp is reset to empty.

If view_account is called, Saddle execute these sequence:

try:
  try:
    content = before_request (was)
    if content:
      return content
    content = view_account (was, *args, **karg)
  except:
    failed_request (was)
  else:
    after_request (was)
finally:
  teardown_request (was)

Also it is possible to bind some events with temporary handling methods.

from skitai.saddle import EVOK, EVEXCEPT, EVREQEND

@app.route ("/view-account")

def view_account (was, userid):
  def handle_ok (was):
      was.temp.user_id
      was.temp.user_status

  was.temp.bind (EVOK, handle_ok)
  was.temp.bind (EVEXCEPT, handle_except)
  was.temp.bind (EVREQEND, handle_end)

  was.temp.user_id = "jerry"
  was.temp.user_status = "active"
  was.temp.resouce = open ()

  return ...

Also there’re another kind of method group,

@app.startup
def startup (wasc, app):
  object = SomeClass ()
  wasc.registerObject ("myobject", object)

@app.onreload
def onreload (wasc, app):
  wasc.myobject.reset ()

@app.shutdown
def shutdown (wasc, app):
  wasc.myobject.close ()

‘wasc’ is class object of ‘was’ and mainly used for sharing Skitai server-wide object. these methods will be called,

  1. startup: when app imported on skitai server started

  2. onreload: when app.use_reloader is True and app is reloaded

  3. shutdown: when skitai server is shutdowned

Using WWW-Authenticate

Saddle provide simple authenticate for administration or perform access control from other system’s call.

app = Saddle (__name__)

app.authorization = "digest"
app.realm = "Partner App Area of mysite.com"
app.user = "app"
app.password = "iamyourpartnerapp"

@app.route ("/hello/<name>")
def hello (was, name = "Hans Roh"):
  return "Hello, %s" % name

If your server run with SSL, you can use app.authorization = “basic”, otherwise recommend using “digest” for your password safety.

Packaging for Larger App

app.py

from skitai.saddle import Saddle
from . import sub

app = Saddle (__name__)
app.debug = True
app.use_reloader = True

app.add_package (sub, "package")

@app.route ("/")
def index (was, num1, num2):
  return was.ab ("hello", "Hans Roh") # url building

sub.py

from skitai.saddle import Package
package = Package ()

@package.route ("/hello/<name>")
def hello (was):
  # can build other module's method url
  return was.ab ("index", 1, 2)

You shoud mount only app.py. App’s debug & use_reloader, etc. attributes will be applied to packages as same.

Implementing XMLRPC Service

Client Side:

import xmlrpc.client as rpc

s = rpc.Server ("http://127.0.0.1:5000/rpc") # RPC App mount point
result = s.add (10000, 5000)

Server Side:

@app.route ("/add")
def index (was, num1, num2):
  return num1 + num2

Is there nothing to diffrence? Yes. Saddle app methods are also used for XMLRPC service if return values are XMLRPC dumpable.

Running Skitai as HTTPS Server

Simply config your certification files to config file (ex. /etc/skitaid/servers-enabled/sample.conf).

[server]
ssl = yes
; added new key
certfile = server.pem
; you can combine to certfile
; keyfile = private.key
; passphrase =

To genrate self-signed certification file:

openssl req -new -newkey rsa:2048 -x509 -keyout server.pem -out server.pem -days 365 -nodes

For more detail please read REAME.txt in /etc/skitaid/cert/README.txt

Skitai with Nginx / Squid

From version 0.10.5, Skitai supports virtual hosting itself, but there’re so many other reasons using with reverse proxy servers.

Here’s some helpful sample works for virtual hosting using Nginx / Squid.

If you want 2 different and totaly unrelated websites:

  • www.jeans.com

  • www.carsales.com

And make two config in /etc/skitaid/servers-enabled

  • jeans.conf using port 5000

  • carsales.conf using port 5001

Then you can reverse proxying using Nginx, Squid or many others.

Example Squid config file (squid.conf) is like this:

http_port 80 accel defaultsite=www.carsales.com

cache_peer 192.168.1.100 parent 5000 0 no-query originserver name=jeans
acl jeans-domain dstdomain www.jeans.com
http_access allow jeans-domain
cache_peer_access jeans allow jeans-domain

cache_peer 192.168.1.100 parent 5001 0 no-query originserver name=carsales
acl carsales-domain dstdomain www.carsales.com
http_access allow carsales-domain
cache_peer_access carsales allow carsales-domain

For Nginx might be 2 config files (I’m not sure):

; /etc/nginx/sites-enabled/jeans.com
server {
        listen 80;
        server_name www.jeans.com;
  location / {
    proxy_pass http://192.168.1.100:5000;
  }
}

; /etc/nginx/sites-enabled/carsales.com
server {
        listen 80;
        server_name www.carsales.com;
  location / {
    proxy_pass http://192.168.1.100:5001;
  }
}

Project Purpose

Skitai App Engine’s original purpose is to serve python fulltext search engine Wissen which is my another pypi work. And recently I found that it is possibly useful for building and serving websites.

Anyway, I am modifying my codes to optimizing for enabling service on Linux machine with relatvely poor H/W and making easy to auto-scaling provided cloud computing service like AWS.

If you need lots of outside http(s) resources connecting jobs and use PostgreSQL, it might be worth testing and participating this project.

Also note it might be more efficient that circumstance using Gevent WSGI Server + Flask. They have well documentation and already tested by lots of users.

Installation and Startup

Posix

sudo pip install skitai
sudo skitaid.py -v &
sudo skitaid.py stop

;if everythig is OK,

sudo service skitaid start

#For auto run on boot,
sudo update-rc.d skitaid defaults
or
sudo chkconfig skitaid on

Win32

sudo pip install skitai
cd c:\skitaid\bin
skitaid.py -v
skitaid.py stop (in another command prompt)

;if everythig is OK,

install-win32-service install

#For auto run on boot,
install-win32-service --startup auto install

install-win32-service start

Requirements

Win 32

Optional Requirements

  • Skitaid can find at least one DNS server from system configuration for Async-DNS query. Possibly it is only problem on dynamic IP allocated desktop, then set DNS manually, please.

  • psycopg2 for querying PostgreSQL asynchronously (win32 binary)

  • Jinja2 for HTML Rendering

Change Log

0.12 - Re-engineering ‘was’ networking, PostgreSQL & proxy modules

0.11 - Websocket implemeted

0.10 - WSGI support

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

skitai-0.12.12.tar.gz (224.0 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page