Archive for the ‘Web’ category

dedicated to cloud & apache to nginx

When I created 11h11.com in 1999 (yes in the 20th century) I had some specific needs that made co-location hosting not an option. I had to learn how to manage a dedicated server running apache, postgresql, php, postfix, vsftp etc.

15 years later, I finally took the time to port this server to the SSD cloud for a fraction of the price. I also opt for using nginx instead of apache. Here’s my note, feel free to poke me if you have any questions.

SECURITY FIRST

When you open a server, the first thing to do is secure it. The most basic steps are:

  • add a regular user
  • tweak sshd_config
  • create iptables
  • install fail2ban

You should set the /etc/hostname – /etc/hosts & dpkg-reconfigure tzdata & reverse dns. Also after installing all your services and before going to production, it is a good idea to scan your server for vulnerabilities. For that you can use a “free PCI scan service”.

INSTALL NGINX STABLE

I am the official repository of nginx to install the stable version of nginx on ubuntu 14.04 lts. You can also compile it from source, but then you will need to keep it updated manually.

  • wget http://nginx.org/keys/nginx_signing.key
  • sudo apt-key add nginx_signing.key
  • nano /etc/apt/sources.list
    deb http://nginx.org/packages/ubuntu/ trusty nginx
    deb-src http://nginx.org/packages/ubuntu/ trusty nginx
  • apt-get update && apt-get install nginx

INSTALL PHP / MYSQL

  • apt-get install php5-cli php5-cgi php5-fpm
  • apt-get install mysql-server php5-mysql
  • apt-get install php5-mcrypt php5-curl php5-gd php5-imagick
  • php5enmod mcrypt gd curl imagick
  • mysql_secure_installation
  • service php5-fpm restart
    • CONFIGURE

      Depending on your instance capacity (mostly ram / cpu), you should tweak nginx, php and mysql settings. Do not use those values. Only a reference for a single core, 1GB ram.

      • nano /etc/nginx/nginx.conf
        user www-data;
        worker_processes  1; # nb of cpu
         
        events {
            worker_connections  1024; # use this ulimit -n to find out
        }
         
        http {
            client_body_buffer_size 10K;
            client_header_buffer_size 1k;
            client_max_body_size 128m; # should be set also in php.ini (upload_max_filesize)
            large_client_header_buffers 2 1k;
        }
        gzip on; # more cpu, less bandwidth - test: http://www.gidnetwork.com/tools/gzip-test.php
        gzip_min_length 10240;
        gzip_proxied expired no-cache no-store private auth;
        gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
        gzip_disable "MSIE [1-6]\.";
         
        server {
        ...
        location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|eot|mp4|ogg|ogv|webm)$ {
            expires max;
            access_log off;
        }
        ...
        }
      • nano /etc/php5/fpm/pool.d/www.conf
        listen.owner = www-data
        listen.group = www-data
        listen.mode = 0666 (not recommended, use 0660)
      • You can use this script to tune Mysql: http://mysqltuner.com
        Or do it manually: sudo nano /etc/mysql/my.cnf

        max_connections = 75
        key_buffer = 32M
        max_allowed_packet = 1M
        thread_stack = 128K
        table_cache = 32
      • sudo nano php5/fpm/php.ini
        error_log = /var/log/php/error.log
        upload_max_filesize = 32M
        post_max_size = 32M
        memory_limit = 128M
      • Install Alternative PHP Cache

      STRESS TEST / SPEEDTEST

      You can now stress test your server using cloud based services (free for 10k clients) or better use apache jmeter. This will give you an idea on how well your server perform.

      To measure bidirectional bandwidth of your server you can install this command-line client:

      sudo apt-get install python-pip
      sudo pip install speedtest-cli
      speedtest-cli (selecting best server based on ping)
      speedtest-cli --list (choose the server (geographically nearest first)):
      speedtest-cli --server 911

      NGINX SERVER{}

      There’s no support for .htaccess in nginx. Everything is configured in the server block. You can use this online tool to convert your apache rewrite rules to nginx.

      • WordPress website
        chown -R www-data.www-data yourwordpresspath/

        server {
            server_name www.yourwordpressdns.com yourwordpressdns;
            root /srv/www/yourwordpresspath/public_html;
            index index.php;
         
            location / {
                try_files $uri $uri/ /index.php?q=$uri&$args;
            }
         
            location ~ \.php$ {
                try_files $uri =404;
                fastcgi_pass unix:/var/run/php5-fpm.sock;
                include /etc/nginx/fastcgi_params;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            }
        }

        Be sure to use a cache plug-in.

      • Kohana 2.4
        server {
        server_name www.kohana2_4.com kohana2_4.com;
        root /srv/www/kohana2_4/public_html;
        index index.php;
         
        location / {
            try_files $uri $uri/ @kohana;
        }
        location ~* \.php$ {
            try_files $uri $uri/ @kohana;
            fastcgi_pass unix:/var/run/php5-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
        location ~* \.html$ {
            try_files $uri $uri/ @kohana;
            fastcgi_pass unix:/var/run/php5-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
        location @kohana
        {
            fastcgi_pass unix:/var/run/php5-fpm.sock;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root/index.php;
        }
        }
      • Nodebb proxy
        server {
            listen 80;
         
            server_name yourforum.yourwebsite.com;
         
            location / {
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_set_header X-NginX-Proxy true;
         
                proxy_pass http://127.0.0.1:4567/;
                proxy_redirect off;
         
                # Socket.IO Support
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
            }
        }

      MAINTENANCE

      I find that most hosting provider are too pricey for back-up solution. I am using a free service (you name it) that give me 25GB of storage. Just install their client (headless). This solution have a drawback: it will use your bandwidth. Here’s 2 script to back-up your websites and mysql database (/etc/cron.daily):

      Mysql:

      #!/bin/sh
      SAUV=/home/backup/mysql/
      TODAY=$(date +%Y%m%d)
      LASTWEEK=$(date --date '1 week ago' +%Y%m%d)
      /usr/bin/mysqldump --user=YOURUSER --password=YOURPASS --lock-all-tables --all-databases > ${SAUV}${TODAY}_mysql.sql
      gzip ${SAUV}${TODAY}_mysql.sql
      if [ -f ${SAUV}${LASTWEEK}_mysql.sql.gz ]
      then
      rm -f ${SAUV}${LASTWEEK}_mysql.sql.gz
      fi

      Website

      #!/bin/sh
      SAUV=/home/bk
      SITE=mywebsite
      DIR=/srv/www/mywebsite/public_html
      TODAY=$(date +%Y%m%d)
      LASTDAY=$(date --date '1 day ago' +%Y%m%d)
      tar -czf ${SAUV}${SITE}_${TODAY}.tar.gz ${DIR}
      if [ -f ${SAUV}${SITE}_${LASTDAY}.tar.gz ]
      then
      rm -f ${SAUV}${SITE}_${LASTDAY}.tar.gz
      fi

      From time to time I run this script (upgrade server, optimize mysql):

      apt-get update
      apt-get upgrade
      apt-get dist-upgrade
      apt-get autoremove
      mysqlcheck --user=YOURUSER --password=YOURPASS--auto-repair --optimize --all-databases
      htop
      df -h
      free -m
      lastlog
      cat /var/log/syslog

      CONTENT DELIVERY NETWORK

      Adding a free CDN might be a good idea:

      • Distribute your content around the world so it’s closer to your visitors
      • Protect your website from a range of online threats (SQL injection, DDOS, etc)
      • If your server is down, your website could still be accessed

01

01 2014

pure data patch repository

pdpatchrepo
http://www.pdpatchrepo.info/

Pure Data have a repository for abstractions and externals, but not for patches. Not anymore, I took the time to code one. There’s multiple ways of searching a patch: by platforms, tags (adc~, notein), is audio, is video, is generative…

The site also feature a live stream (video & audio) with networked gui so that multiple visitors can interact with the streaming patch. Of course there’s a latency in the feed when playing with the knobs (betweeen 3-5 seconds) but it is still a fun way to jam with others. The bandwidth is provided by the Institute of Electronic Music and Acoustics.

RSS feeds

Patch (when a new patch is added)
Stream (when a new patch is stream)

Screenshot

31

10 2013

PunBB to NodeBB

Here’s my note that I took when I transferred the Pure Data forum in NodeBB from PunBB. NodeBB is coded in Node.js and use Redis or MongoDB.

Install Node.js & Redis

I’m using Redis (all in memory / faster) but you can use MongoDB.

sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update && sudo apt-get dist-upgrade
sudo apt-get install redis-server

Preparation

  • MySQL dump the PunBB forum
  • tar.gz the attach folder
  • put the PunBB forum in maintenance
  • import the database and run fixPunBB.sql
  • rename and copy the attachment files using attach.php

attach.php

<?php
$link = mysql_connect('localhost', 'xxx', 'xxx');
mysql_select_db('xxx');
$result = mysql_query("SELECT punbb_attach_2_files.location, replace(replace(replace(punbb_attach_2_files.filename, ' ', '_'), '#', '_'), '%20', '_') FROM punbb_attach_2_files");
while ($row = mysql_fetch_array($result, MYSQL_NUM)) {
    printf("ID: %s FILE: %s\n" , $row[0], $row[1]);
    rename($row[0], "/var/www/oldpunbbattachment/".$row[1]);
}
mysql_close($link);
?>

fixPunBB.sql

ALTER TABLE punbb_attach_2_files ADD INDEX (post_id);
optimize table punbb_attach_2_files;
drop table if exists punbb_posts_tmp;
create table punbb_posts_tmp_tmp as (select
punbb_posts.id as id
from punbb_topics, punbb_posts  
where punbb_topics.id = punbb_posts.topic_id AND punbb_posts.id IN(SELECT MIN(punbb_posts.id) FROM punbb_posts where punbb_posts.topic_id = punbb_topics.id));
create index punbb_posts_tmp_tmp_idx on punbb_posts_tmp_tmp (id);
create table punbb_posts_tmp as (select * from punbb_posts where id not in (select id from punbb_posts_tmp_tmp));
drop table punbb_posts_tmp_tmp;

Export

Based on the work of https://github.com/akhoury/nodebb-plugin-import-ubb:

git clone https://github.com/patricksebastien/nodebb-plugin-import-punbb.git
cd nodebb-plugin-import-punbb && edit export.config.json (database access)

The repo is for a generic export, but here’s what I actually used when exporting the Pure Data forum. Edit lib/export.js and change the 2 sql queries for topics / posts:

// topics
select
punbb_topics.id as _tid,
punbb_topics.forum_id as _cid,
punbb_posts.id as _pid,
punbb_posts.poster_id as _uid,
punbb_topics.num_views as _viewcount,
punbb_topics.subject as _title,
punbb_topics.posted as _timestamp,
punbb_posts.topic_id as _post_tid,
if(punbb_attach_2_files.filename is null, punbb_posts.message, concat(punbb_posts.message, '\n\n[url]http://www.pdpatchrepo.info/hurleur/', replace(replace(replace(punbb_attach_2_files.filename, ' ', '_'), '#', '_'), '%20', '_'), '[/url]')) as _content
from punbb_topics, punbb_posts
left join punbb_attach_2_files
on punbb_attach_2_files.post_id = punbb_posts.id
where punbb_topics.id = punbb_posts.topic_id AND punbb_posts.id IN(SELECT MIN(punbb_posts.id) FROM punbb_posts where punbb_posts.topic_id = punbb_topics.id)

// posts
select
punbb_attach_2_files.filename,
punbb_posts_tmp.id as _pid,
topic_id as _tid,
posted as _timestamp,
if(punbb_attach_2_files.filename is null, punbb_posts_tmp.message, concat(punbb_posts_tmp.message, '\n\n[url]http://www.pdpatchrepo.info/hurleur/', replace(replace(replace(punbb_attach_2_files.filename, ' ', '_'), '#', '_'), '%20', '_'), '[/url]')) as _content,
poster_id as _uid
from punbb_posts_tmp
left join punbb_attach_2_files
on punbb_attach_2_files.post_id = punbb_posts_tmp.id
order by punbb_posts_tmp.posted

Run the export!

node bin/export.js --storage="storage" --config="./export.config.json" --flush

Install NodeBB

You need to use a specific version of NodeBB:

git clone git://github.com/NodeBB/NodeBB.git nodebb && cd nodebb
git checkout 206acab1bfab4be0f9073f7f885b773c5942ead2
npm install
./nodebb setup
edit /etc/redis/redis.conf (requirepass)

Import

Now it’s time to import from PunBB to NodeBB.

npm install nodebb-plugin-import
cd node_modules/nodebb-plugin-import
nano import.config.json (see below)
node import.js --config="./import.config.json" | tee import.log
cd ../../
git checkout master
npm install
./nodebb setup
./nodebb upgrade

import.config.json

{
    "log": "debug",
    "passwordGen": {
        "enabled": true,
        "chars": "{}.-_=+qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890",
        "len": 8
    },
    "redirectTemplatesStrings": {
        "users": null,
        "categories": null,
        "topics": null,
        "posts": null
    },
    "storageDir": "/path/to/storage",
    "convert": "bbcode-to-md",
    "nbb": {
        "setup": {
            "runFlush": true,
            "adminConfig": {
                "admin:username": "user",
                "admin:password": "pwd",
                "admin:password:confirm": "pwd",
                "admin:email": "admin@dns.com"
            },
            "fileConfig": null
        },
        "autoConfirmEmails": true
    }
}

Send email to users

Now you need to tell your users about the new forum and also that they need to change their password. You can use Mandrill and this excellent web-interface: http://akhoury.github.io/pages/mandrill-blast/

rgrep "user-csv" ./node_modules/nodebb-plugin-import/import.log | cut -d "]" -f 2 | sed -e 's/^[ \t]*//' > user.csv

Start NODEBB on boot

I’m using an UpStart script. Be sure to “chown -R nodebb.nodebb /srv/www/youforumpath/nodebb”.

start on startup
stop on runlevel [016]

respawn

setuid nodebb
setgid nodebb

script
    cd /srv/www/youforumpath/nodebb
    ./nodebb start
end script

Finish installation

Install some plugins:
youtube, soundcloud, linkchecks, nodebb-plugin-vimeo, twitter, mandrill
npm install nodebb-plugin-bbcode-to-markdown
npm install nodebb-plugin-spam-be-gone

Here’s some themes:
npm install nodebb-theme-ifsta-ui
npm install nodebb-theme-rocket
npm install nodebb-theme-cerulean
npm install nodebb-theme-blacknred
npm install nodebb-theme-purplish
npm install nodebb-theme-dark-rectangles

Apache proxy

If you can use Nginx or Apache 2.4! But if you’re like me stuck with Apache and a very old version of it then you need to patch it. The patch is only for Apache 2.2, but I modified it to work with Apache 2.14:

diff -Naur httpd-2.2.24.orig/modules/proxy/config.m4 httpd-2.2.24/modules/proxy/config.m4
--- httpd-2.2.24.orig/modules/proxy/config.m4   2013-04-25 21:08:56.414107928 +0200
+++ httpd-2.2.24/modules/proxy/config.m4    2013-04-25 21:08:58.474145010 +0200
@@ -18,6 +18,7 @@
 proxy_http_objs="mod_proxy_http.lo"
 proxy_scgi_objs="mod_proxy_scgi.lo"
 proxy_ajp_objs="mod_proxy_ajp.lo ajp_header.lo ajp_link.lo ajp_msg.lo ajp_utils.lo"
+proxy_wstunnel_objs="mod_proxy_wstunnel.lo"
 proxy_balancer_objs="mod_proxy_balancer.lo"
 
 case "$host" in
@@ -29,6 +30,7 @@
     proxy_http_objs="$proxy_http_objs mod_proxy.la"
     proxy_scgi_objs="$proxy_scgi_objs mod_proxy.la"
     proxy_ajp_objs="$proxy_ajp_objs mod_proxy.la"
+    proxy_wstunnel_objs="$proxy_wstunnel_objs mod_proxy.la"
     proxy_balancer_objs="$proxy_balancer_objs mod_proxy.la"
     ;;
 esac
@@ -37,6 +39,7 @@
 APACHE_MODULE(proxy_ftp, Apache proxy FTP module, $proxy_ftp_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_http, Apache proxy HTTP module, $proxy_http_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_scgi, Apache proxy SCGI module, $proxy_scgi_objs, , $proxy_mods_enable)
+APACHE_MODULE(proxy_wstunnel, Apache proxy Websocket Tunnel module, $proxy_wstunnel_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_ajp, Apache proxy AJP module, $proxy_ajp_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_balancer, Apache proxy BALANCER module, $proxy_balancer_objs, , $proxy_mods_enable)
 
diff -Naur httpd-2.2.24.orig/modules/proxy/mod_proxy.h httpd-2.2.24/modules/proxy/mod_proxy.h
--- httpd-2.2.24.orig/modules/proxy/mod_proxy.h 2013-04-25 21:08:56.414107928 +0200
+++ httpd-2.2.24/modules/proxy/mod_proxy.h  2013-04-25 21:08:58.477478403 +0200
@@ -770,6 +770,46 @@
 ap_proxy_buckets_lifetime_transform(request_rec *r, apr_bucket_brigade *from,
                                         apr_bucket_brigade *to);
 
+/**
+ * Create a HTTP request header brigade,  old_cl_val and old_te_val as required.
+ * @parama p              pool
+ * @param header_brigade  header brigade to use/fill
+ * @param r               request
+ * @param p_conn          proxy connection rec
+ * @param worker          selected worker
+ * @param conf            per-server proxy config
+ * @param uri             uri
+ * @param url             url
+ * @param server_portstr  port as string
+ * @param old_cl_val      stored old content-len val
+ * @param old_te_val      stored old TE val
+ * @return                OK or HTTP_EXPECTATION_FAILED
+ */
+PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
+                                           apr_bucket_brigade *header_brigade,
+                                           request_rec *r,
+                                           proxy_conn_rec *p_conn,
+                                           proxy_worker *worker,
+                                           proxy_server_conf *conf,
+                                           apr_uri_t *uri,
+                                           char *url, char *server_portstr,
+                                           char **old_cl_val,
+                                           char **old_te_val);
+
+/**
+ * @param bucket_alloc  bucket allocator
+ * @param r             request
+ * @param p_conn        proxy connection
+ * @param origin        connection rec of origin
+ * @param  bb           brigade to send to origin
+ * @param  flush        flush
+ * @return              status (OK)
+ */
+PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
+                                         request_rec *r, proxy_conn_rec *p_conn,
+                                         conn_rec *origin, apr_bucket_brigade *bb,
+                                         int flush);
+
 #define PROXY_LBMETHOD "proxylbmethod"
 
 /* The number of dynamic workers that can be added when reconfiguring.
diff -Naur httpd-2.2.24.orig/modules/proxy/mod_proxy_wstunnel.c httpd-2.2.24/modules/proxy/mod_proxy_wstunnel.c
--- httpd-2.2.24.orig/modules/proxy/mod_proxy_wstunnel.c    1970-01-01 01:00:00.000000000 +0100
+++ httpd-2.2.24/modules/proxy/mod_proxy_wstunnel.c 2013-04-25 21:09:09.871016830 +0200
@@ -0,0 +1,400 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+
+module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
+
+/*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+ * url is the URL starting with the first '/'
+ * def_port is the default port for this scheme.
+ */
+static int proxy_wstunnel_canon(request_rec *r, char *url)
+{
+    char *host, *path, sport[7];
+    char *search = NULL;
+    const char *err;
+    char *scheme;
+    apr_port_t port, def_port;
+
+    /* ap_port_of_scheme() */
+    if (strncasecmp(url, "ws:", 3) == 0) {
+        url += 3;
+        scheme = "ws:";
+        def_port = apr_uri_port_of_scheme("http");
+    }
+    else if (strncasecmp(url, "wss:", 4) == 0) {
+        url += 4;
+        scheme = "wss:";
+        def_port = apr_uri_port_of_scheme("https");
+    }
+    else {
+        return DECLINED;
+    }
+
+    port = def_port;
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "canonicalising URL %s", url);
+
+    /*
+     * do syntactic check.
+     * We break the URL into host, port, path, search
+     */
+    err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+    if (err) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "AH02439: " "error parsing URL %s: %s",
+                      url, err);
+        return HTTP_BAD_REQUEST;
+    }
+
+    /*
+     * now parse path/search args, according to rfc1738:
+     * process the path. With proxy-nocanon set (by
+     * mod_proxy) we use the raw, unparsed uri
+     */
+    if (apr_table_get(r->notes, "proxy-nocanon")) {
+        path = url;   /* this is the raw path */
+    }
+    else {
+        path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+                                 r->proxyreq);
+        search = r->args;
+    }
+    if (path == NULL)
+        return HTTP_BAD_REQUEST;
+
+    apr_snprintf(sport, sizeof(sport), ":%d", port);
+
+    if (ap_strchr_c(host, ':')) {
+        /* if literal IPv6 address */
+        host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+    }
+    r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "//", host, sport,
+                              "/", path, (search) ? "?" : "",
+                              (search) ? search : "", NULL);
+    return OK;
+}
+
+
+static int proxy_wstunnel_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o,
+                                     apr_bucket_brigade *bb, char *name)
+{
+    int rv;
+#ifdef DEBUGGING
+    apr_off_t len;
+#endif
+
+    do {
+        apr_brigade_cleanup(bb);
+        rv = ap_get_brigade(c_i->input_filters, bb, AP_MODE_READBYTES,
+                            APR_NONBLOCK_READ, AP_IOBUFSIZE);
+        if (rv == APR_SUCCESS) {
+            if (c_o->aborted)
+                return APR_EPIPE;
+            if (APR_BRIGADE_EMPTY(bb))
+                break;
+#ifdef DEBUGGING
+            len = -1;
+            apr_brigade_length(bb, 0, &len);
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02440: "
+                          "read %" APR_OFF_T_FMT
+                          " bytes from %s", len, name);
+#endif
+            rv = ap_pass_brigade(c_o->output_filters, bb);
+            if (rv == APR_SUCCESS) {
+                ap_fflush(c_o->output_filters, bb);
+            }
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "AH02441: "
+                              "error on %s - ap_pass_brigade",
+                              name);
+            }
+        } else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "AH02442: "
+                          "error on %s - ap_get_brigade",
+                          name);
+        }
+    } while (rv == APR_SUCCESS);
+
+    if (APR_STATUS_IS_EAGAIN(rv)) {
+        rv = APR_SUCCESS;
+    }
+    return rv;
+}
+
+/* Search thru the input filters and remove the reqtimeout one */
+static void remove_reqtimeout(ap_filter_t *next)
+{
+    ap_filter_t *reqto = NULL;
+    ap_filter_rec_t *filter;
+
+    filter = ap_get_input_filter_handle("reqtimeout");
+    if (!filter) {
+        return;
+    }
+
+    while (next) {
+        if (next->frec == filter) {
+            reqto = next;
+            break;
+        }
+        next = next->next;
+    }
+    if (reqto) {
+        ap_remove_input_filter(reqto);
+    }
+}
+
+/*
+ * process the request and write the response.
+ */
+static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
+                                proxy_conn_rec *conn,
+                                proxy_worker *worker,
+                                proxy_server_conf *conf,
+                                apr_uri_t *uri,
+                                char *url, char *server_portstr)
+{
+    apr_status_t rv = APR_SUCCESS;
+    apr_pollset_t *pollset;
+    apr_pollfd_t pollfd;
+    const apr_pollfd_t *signalled;
+    apr_int32_t pollcnt, pi;
+    apr_int16_t pollevent;
+    conn_rec *c = r->connection;
+    apr_socket_t *sock = conn->sock;
+    conn_rec *backconn = conn->connection;
+    int client_error = 0;
+    char *buf;
+    apr_bucket_brigade *header_brigade;
+    apr_bucket *e;
+    char *old_cl_val = NULL;
+    char *old_te_val = NULL;
+    apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
+    apr_socket_t *client_socket = ap_get_module_config(c->conn_config, &core_module);
+
+    header_brigade = apr_brigade_create(p, backconn->bucket_alloc);
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "sending request");
+
+    rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, conn,
+                                 worker, conf, uri, url, server_portstr,
+                                 &old_cl_val, &old_te_val);
+    if (rv != OK) {
+        return rv;
+    }
+
+    buf = apr_pstrcat(p, "Upgrade: WebSocket", CRLF, "Connection: Upgrade", CRLF, CRLF, NULL);
+    ap_xlate_proto_to_ascii(buf, strlen(buf));
+    e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+
+    if ((rv = ap_proxy_pass_brigade(c->bucket_alloc, r, conn, backconn,
+                                    header_brigade, 1)) != OK)
+        return rv;
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "setting up poll()");
+
+    if ((rv = apr_pollset_create(&pollset, 2, p, 0)) != APR_SUCCESS) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "AH02443: "
+                      "error apr_pollset_create()");
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+#if 0
+    apr_socket_opt_set(sock, APR_SO_NONBLOCK, 1);
+    apr_socket_opt_set(sock, APR_SO_KEEPALIVE, 1);
+    apr_socket_opt_set(client_socket, APR_SO_NONBLOCK, 1);
+    apr_socket_opt_set(client_socket, APR_SO_KEEPALIVE, 1);
+#endif
+
+    pollfd.p = p;
+    pollfd.desc_type = APR_POLL_SOCKET;
+    pollfd.reqevents = APR_POLLIN;
+    pollfd.desc.s = sock;
+    pollfd.client_data = NULL;
+    apr_pollset_add(pollset, &pollfd);
+
+    pollfd.desc.s = client_socket;
+    apr_pollset_add(pollset, &pollfd);
+
+
+    r->output_filters = c->output_filters;
+    r->proto_output_filters = c->output_filters;
+    r->input_filters = c->input_filters;
+    r->proto_input_filters = c->input_filters;
+
+    remove_reqtimeout(r->input_filters);
+
+    while (1) { /* Infinite loop until error (one side closes the connection) */
+        if ((rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled))
+            != APR_SUCCESS) {
+            if (APR_STATUS_IS_EINTR(rv)) {
+                continue;
+            }
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "AH02444: " "error apr_poll()");
+            return HTTP_INTERNAL_SERVER_ERROR;
+        }
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02445: "
+                      "woke from poll(), i=%d", pollcnt);
+
+        for (pi = 0; pi < pollcnt; pi++) {
+            const apr_pollfd_t *cur = &signalled[pi];
+
+            if (cur->desc.s == sock) {
+                pollevent = cur->rtnevents;
+                if (pollevent & APR_POLLIN) {
+                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02446: "
+                                  "sock was readable");
+                    rv = proxy_wstunnel_transfer(r, backconn, c, bb, "sock");
+                    }
+                else if ((pollevent & APR_POLLERR)
+                         || (pollevent & APR_POLLHUP)) {
+                         rv = APR_EPIPE;
+                         ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "AH02447: "
+                                       "err/hup on backconn");
+                }
+                if (rv != APR_SUCCESS)
+                    client_error = 1;
+            }
+            else if (cur->desc.s == client_socket) {
+                pollevent = cur->rtnevents;
+                if (pollevent & APR_POLLIN) {
+                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02448: "
+                                  "client was readable");
+                    rv = proxy_wstunnel_transfer(r, c, backconn, bb, "client");
+                }
+            }
+            else {
+                rv = APR_EBADF;
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "AH02449: "
+                              "unknown socket in pollset");
+            }
+
+        }
+        if (rv != APR_SUCCESS) {
+            break;
+        }
+    }
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                  "finished with poll() - cleaning up");
+
+    if (client_error) {
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+    return OK;
+}
+
+/*
+ */
+static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
+                             proxy_server_conf *conf,
+                             char *url, const char *proxyname,
+                             apr_port_t proxyport)
+{
+    int status;
+    char server_portstr[32];
+    proxy_conn_rec *backend = NULL;
+    char *scheme;
+    int retry;
+    conn_rec *c = r->connection;
+    apr_pool_t *p = r->pool;
+    apr_uri_t *uri;
+
+    if (strncasecmp(url, "wss:", 4) == 0) {
+        scheme = "WSS";
+    }
+    else if (strncasecmp(url, "ws:", 3) == 0) {
+        scheme = "WS";
+    }
+    else {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02450: " "declining URL %s", url);
+        return DECLINED;
+    }
+
+    uri = apr_palloc(p, sizeof(*uri));
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "AH02451: " "serving URL %s", url);
+
+    /* create space for state information */
+    status = ap_proxy_acquire_connection(scheme, &backend, worker,
+                                         r->server);
+    if (status != OK) {
+        if (backend) {
+            backend->close = 1;
+            ap_proxy_release_connection(scheme, backend, r->server);
+        }
+        return status;
+    }
+
+    backend->is_ssl = 0;
+    backend->close = 0;
+
+    retry = 0;
+    while (retry < 2) {
+        char *locurl = url;
+        /* Step One: Determine Who To Connect To */
+        status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+                                               uri, &locurl, proxyname, proxyport,
+                                               server_portstr,
+                                               sizeof(server_portstr));
+
+        if (status != OK)
+            break;
+
+        /* Step Two: Make the Connection */
+        if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "AH02452: "
+                          "failed to make connection to backend: %s",
+                          backend->hostname);
+            status = HTTP_SERVICE_UNAVAILABLE;
+            break;
+        }
+        /* Step Three: Create conn_rec */
+        if (!backend->connection) {
+            if ((status = ap_proxy_connection_create(scheme, backend,
+                                                     c, r->server)) != OK)
+                break;
+         }
+
+        /* Step Three: Process the Request */
+        status = ap_proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl,
+                                      server_portstr);
+        break;
+    }
+
+    /* Do not close the socket */
+    ap_proxy_release_connection(scheme, backend, r->server);
+    return status;
+}
+
+static void ap_proxy_http_register_hook(apr_pool_t *p)
+{
+    proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST);
+    proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
+APLOG_USE_MODULE(proxy_wstunnel);
+module AP_MODULE_DECLARE_DATA proxy_wstunnel_module = {
+    STANDARD20_MODULE_STUFF,
+    NULL,                       /* create per-directory config structure */
+    NULL,                       /* merge per-directory config structures */
+    NULL,                       /* create per-server config structure */
+    NULL,                       /* merge per-server config structures */
+    NULL,                       /* command apr_table_t */
+    ap_proxy_http_register_hook /* register hooks */
+};
diff -Naur httpd-2.2.24.orig/modules/proxy/mod_proxy_wstunnel.dsp httpd-2.2.24/modules/proxy/mod_proxy_wstunnel.dsp
--- httpd-2.2.24.orig/modules/proxy/mod_proxy_wstunnel.dsp  1970-01-01 01:00:00.000000000 +0100
+++ httpd-2.2.24/modules/proxy/mod_proxy_wstunnel.dsp   2013-04-25 21:08:58.480811797 +0200
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_wstunnel" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_wstunnel - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_wstunnel.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_wstunnel.mak" CFG="mod_proxy_wstunnel - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_wstunnel - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_wstunnel - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "mod_proxy_wstunnel - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_wstunnel_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_wstunnel.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF  "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_wstunnel_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_wstunnel.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_wstunnel - Win32 Release"
+# Name "mod_proxy_wstunnel - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_wstunnel.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff -Naur httpd-2.2.24.orig/modules/proxy/NWGNUmakefile httpd-2.2.24/modules/proxy/NWGNUmakefile
--- httpd-2.2.24.orig/modules/proxy/NWGNUmakefile   2013-04-25 21:08:56.417441322 +0200
+++ httpd-2.2.24/modules/proxy/NWGNUmakefile    2013-04-25 21:08:58.480811797 +0200
@@ -159,6 +159,7 @@
    $(OBJDIR)/proxybalancer.nlm \
    $(OBJDIR)/proxyajp.nlm \
    $(OBJDIR)/proxyscgi.nlm \
+   $(OBJDIR)/proxywstunnel.nlm \
    $(EOLIST)
 
 #
diff -Naur httpd-2.2.24.orig/modules/proxy/NWGNUproxywstunnel httpd-2.2.24/modules/proxy/NWGNUproxywstunnel
--- httpd-2.2.24.orig/modules/proxy/NWGNUproxywstunnel  1970-01-01 01:00:00.000000000 +0100
+++ httpd-2.2.24/modules/proxy/NWGNUproxywstunnel   2013-04-25 21:08:58.480811797 +0200
@@ -0,0 +1,250 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary.  This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS   += \
+           $(APR)/include \
+           $(APRUTIL)/include \
+           $(SRC)/include \
+           $(STDMOD)/http \
+           $(STDMOD)/proxy \
+           $(NWOS) \
+           $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS        += \
+           $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES   += \
+           $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS        += \
+           $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS   += \
+           $(EOLIST)
+
+XCFLAGS        += \
+           $(EOLIST)
+
+XDEFINES   += \
+           $(EOLIST)
+
+XLFLAGS        += \
+           $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS   += \
+           $(EOLIST)
+
+XCFLAGS        += \
+           $(EOLIST)
+
+XDEFINES   += \
+           $(EOLIST)
+
+XLFLAGS        += \
+           $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS   += \
+           $(EOLIST)
+
+XCFLAGS        += \
+           $(EOLIST)
+
+XDEFINES   += \
+           $(EOLIST)
+
+XLFLAGS        += \
+           $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm.  If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME   = proxywstunnel
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION    = Apache $(VERSION_STR) Proxy Web Socket Tunnel Module
+
+#
+# This is used by the '-threadname' directive.  If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME    = Prxy WbSkt Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION    =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM  =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM   =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM  =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS  =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc.  XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA        =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib =
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+   $(OBJDIR)/mod_proxy_wstunnel.o \
+   $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+   $(PRELUDE) \
+   $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+   libc \
+   aprlib \
+   proxy \
+   $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+   @libc.imp \
+   @aprlib.imp \
+   @httpd.imp \
+   @$(OBJDIR)/mod_proxy.imp \
+   $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+   proxy_wstunnel_module \
+   $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+   $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place.  (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c balancers
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff -Naur httpd-2.2.24.orig/modules/proxy/proxy_util.c httpd-2.2.24/modules/proxy/proxy_util.c
--- httpd-2.2.24.orig/modules/proxy/proxy_util.c    2013-04-25 21:08:56.417441322 +0200
+++ httpd-2.2.24/modules/proxy/proxy_util.c 2013-04-25 21:08:58.484145190 +0200
@@ -2680,3 +2680,329 @@
     }
     return rv;
 }
+
+/* Clear all connection-based headers from the incoming headers table */
+typedef struct header_dptr {
+    apr_pool_t *pool;
+    apr_table_t *table;
+    apr_time_t time;
+} header_dptr;
+
+static int clear_conn_headers(void *data, const char *key, const char *val)
+{
+    apr_table_t *headers = ((header_dptr*)data)->table;
+    apr_pool_t *pool = ((header_dptr*)data)->pool;
+    const char *name;
+    char *next = apr_pstrdup(pool, val);
+    while (*next) {
+        name = next;
+        while (*next && !apr_isspace(*next) && (*next != ',')) {
+            ++next;
+        }
+        while (*next && (apr_isspace(*next) || (*next == ','))) {
+            *next++ = '\0';
+        }
+        apr_table_unset(headers, name);
+    }
+    return 1;
+}
+
+static void proxy_clear_connection(apr_pool_t *p, apr_table_t *headers)
+{
+    header_dptr x;
+    x.pool = p;
+    x.table = headers;
+    apr_table_unset(headers, "Proxy-Connection");
+    apr_table_do(clear_conn_headers, &x, headers, "Connection", NULL);
+    apr_table_unset(headers, "Connection");
+}
+
+PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
+                                            apr_bucket_brigade *header_brigade,
+                                            request_rec *r,
+                                            proxy_conn_rec *p_conn,
+                                            proxy_worker *worker,
+                                            proxy_server_conf *conf,
+                                            apr_uri_t *uri,
+                                            char *url, char *server_portstr,
+                                            char **old_cl_val,
+                                            char **old_te_val)
+{
+    conn_rec *c = r->connection;
+    int counter;
+    char *buf;
+    const apr_array_header_t *headers_in_array;
+    const apr_table_entry_t *headers_in;
+    apr_table_t *headers_in_copy;
+    apr_bucket *e;
+    int do_100_continue;
+    conn_rec *origin = p_conn->connection;
+    proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+
+    /*
+     * To be compliant, we only use 100-Continue for requests with bodies.
+     * We also make sure we won't be talking HTTP/1.0 as well.
+     */
+    do_100_continue = (worker->ping_timeout_set
+                       && !r->header_only
+                       && (apr_table_get(r->headers_in, "Content-Length")
+                          || apr_table_get(r->headers_in, "Transfer-Encoding"))
+                       && (PROXYREQ_REVERSE == r->proxyreq)
+                       && !(apr_table_get(r->subprocess_env, "force-proxy-request-1.0")));
+
+    if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
+        /*
+         * According to RFC 2616 8.2.3 we are not allowed to forward an
+         * Expect: 100-continue to an HTTP/1.0 server. Instead we MUST return
+         * a HTTP_EXPECTATION_FAILED
+         */
+        if (r->expecting_100) {
+            return HTTP_EXPECTATION_FAILED;
+        }
+        buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL);
+        p_conn->close = 1;
+    } else {
+        buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL);
+    }
+    if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
+        origin->keepalive = AP_CONN_CLOSE;
+        p_conn->close = 1;
+    }
+    ap_xlate_proto_to_ascii(buf, strlen(buf));
+    e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+    if (conf->preserve_host == 0) {
+        if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */
+            if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
+                buf = apr_pstrcat(p, "Host: [", uri->hostname, "]:",
+                                  uri->port_str, CRLF, NULL);
+            } else {
+                buf = apr_pstrcat(p, "Host: [", uri->hostname, "]", CRLF, NULL);
+            }
+        } else {
+            if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
+                buf = apr_pstrcat(p, "Host: ", uri->hostname, ":",
+                                  uri->port_str, CRLF, NULL);
+            } else {
+                buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, NULL);
+            }
+        }
+    }
+    else {
+        /* don't want to use r->hostname, as the incoming header might have a
+         * port attached
+         */
+        const char* hostname = apr_table_get(r->headers_in,"Host");
+        if (!hostname) {
+            hostname =  r->server->server_hostname;
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "AH01092: "
+                          "no HTTP 0.9 request (with no host line) "
+                          "on incoming request and preserve host set "
+                          "forcing hostname to be %s for uri %s",
+                          hostname, r->uri);
+        }
+        buf = apr_pstrcat(p, "Host: ", hostname, CRLF, NULL);
+    }
+    ap_xlate_proto_to_ascii(buf, strlen(buf));
+    e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+
+    /* handle Via */
+    if (conf->viaopt == via_block) {
+        /* Block all outgoing Via: headers */
+        apr_table_unset(r->headers_in, "Via");
+    } else if (conf->viaopt != via_off) {
+        const char *server_name = ap_get_server_name(r);
+        /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
+         * then the server name returned by ap_get_server_name() is the
+         * origin server name (which does make too much sense with Via: headers)
+         * so we use the proxy vhost's name instead.
+         */
+        if (server_name == r->hostname)
+            server_name = r->server->server_hostname;
+        /* Create a "Via:" request header entry and merge it */
+        /* Generate outgoing Via: header with/without server comment: */
+        apr_table_mergen(r->headers_in, "Via",
+                         (conf->viaopt == via_full)
+                         ? apr_psprintf(p, "%d.%d %s%s (%s)",
+                                        HTTP_VERSION_MAJOR(r->proto_num),
+                                        HTTP_VERSION_MINOR(r->proto_num),
+                                        server_name, server_portstr,
+                                        AP_SERVER_BASEVERSION)
+                         : apr_psprintf(p, "%d.%d %s%s",
+                                        HTTP_VERSION_MAJOR(r->proto_num),
+                                        HTTP_VERSION_MINOR(r->proto_num),
+                                        server_name, server_portstr)
+                         );
+    }
+
+    /* Use HTTP/1.1 100-Continue as quick "HTTP ping" test
+     * to backend
+     */
+    if (do_100_continue) {
+        apr_table_mergen(r->headers_in, "Expect", "100-Continue");
+        r->expecting_100 = 1;
+    }
+
+    /* X-Forwarded-*: handling
+     *
+     * XXX Privacy Note:
+     * -----------------
+     *
+     * These request headers are only really useful when the mod_proxy
+     * is used in a reverse proxy configuration, so that useful info
+     * about the client can be passed through the reverse proxy and on
+     * to the backend server, which may require the information to
+     * function properly.
+     *
+     * In a forward proxy situation, these options are a potential
+     * privacy violation, as information about clients behind the proxy
+     * are revealed to arbitrary servers out there on the internet.
+     *
+     * The HTTP/1.1 Via: header is designed for passing client
+     * information through proxies to a server, and should be used in
+     * a forward proxy configuation instead of X-Forwarded-*. See the
+     * ProxyVia option for details.
+     */
+    if (PROXYREQ_REVERSE == r->proxyreq) {
+        const char *buf;
+
+        /* Add X-Forwarded-For: so that the upstream has a chance to
+         * determine, where the original request came from.
+         */
+        apr_table_mergen(r->headers_in, "X-Forwarded-For",
+                         c->remote_ip);
+
+        /* Add X-Forwarded-Host: so that upstream knows what the
+         * original request hostname was.
+         */
+        if ((buf = apr_table_get(r->headers_in, "Host"))) {
+            apr_table_mergen(r->headers_in, "X-Forwarded-Host", buf);
+        }
+
+        /* Add X-Forwarded-Server: so that upstream knows what the
+         * name of this proxy server is (if there are more than one)
+         * XXX: This duplicates Via: - do we strictly need it?
+         */
+        apr_table_mergen(r->headers_in, "X-Forwarded-Server",
+                         r->server->server_hostname);
+    }
+
+    proxy_run_fixups(r);
+    /*
+     * Make a copy of the headers_in table before clearing the connection
+     * headers as we need the connection headers later in the http output
+     * filter to prepare the correct response headers.
+     *
+     * Note: We need to take r->pool for apr_table_copy as the key / value
+     * pairs in r->headers_in have been created out of r->pool and
+     * p might be (and actually is) a longer living pool.
+     * This would trigger the bad pool ancestry abort in apr_table_copy if
+     * apr is compiled with APR_POOL_DEBUG.
+     */
+    headers_in_copy = apr_table_copy(r->pool, r->headers_in);
+    proxy_clear_connection(p, headers_in_copy);
+    /* send request headers */
+    headers_in_array = apr_table_elts(headers_in_copy);
+    headers_in = (const apr_table_entry_t *) headers_in_array->elts;
+    for (counter = 0; counter < headers_in_array->nelts; counter++) {
+        if (headers_in[counter].key == NULL
+            || headers_in[counter].val == NULL
+
+            /* Already sent */
+            || !strcasecmp(headers_in[counter].key, "Host")
+
+            /* Clear out hop-by-hop request headers not to send
+             * RFC2616 13.5.1 says we should strip these headers
+             */
+            || !strcasecmp(headers_in[counter].key, "Keep-Alive")
+            || !strcasecmp(headers_in[counter].key, "TE")
+            || !strcasecmp(headers_in[counter].key, "Trailer")
+            || !strcasecmp(headers_in[counter].key, "Upgrade")
+
+            ) {
+            continue;
+        }
+        /* Do we want to strip Proxy-Authorization ?
+         * If we haven't used it, then NO
+         * If we have used it then MAYBE: RFC2616 says we MAY propagate it.
+         * So let's make it configurable by env.
+         */
+        if (!strcasecmp(headers_in[counter].key,"Proxy-Authorization")) {
+            if (r->user != NULL) { /* we've authenticated */
+                if (!apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
+                    continue;
+                }
+            }
+        }
+
+        /* Skip Transfer-Encoding and Content-Length for now.
+         */
+        if (!strcasecmp(headers_in[counter].key, "Transfer-Encoding")) {
+            *old_te_val = headers_in[counter].val;
+            continue;
+        }
+        if (!strcasecmp(headers_in[counter].key, "Content-Length")) {
+            *old_cl_val = headers_in[counter].val;
+            continue;
+        }
+
+        /* for sub-requests, ignore freshness/expiry headers */
+        if (r->main) {
+            if (    !strcasecmp(headers_in[counter].key, "If-Match")
+                || !strcasecmp(headers_in[counter].key, "If-Modified-Since")
+                || !strcasecmp(headers_in[counter].key, "If-Range")
+                || !strcasecmp(headers_in[counter].key, "If-Unmodified-Since")
+                || !strcasecmp(headers_in[counter].key, "If-None-Match")) {
+                continue;
+            }
+        }
+
+        buf = apr_pstrcat(p, headers_in[counter].key, ": ",
+                          headers_in[counter].val, CRLF,
+                          NULL);
+        ap_xlate_proto_to_ascii(buf, strlen(buf));
+        e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+    }
+    return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
+                                         request_rec *r, proxy_conn_rec *p_conn,
+                                         conn_rec *origin, apr_bucket_brigade *bb,
+                                         int flush)
+{
+    apr_status_t status;
+    apr_off_t transferred;
+
+    if (flush) {
+        apr_bucket *e = apr_bucket_flush_create(bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(bb, e);
+    }
+    apr_brigade_length(bb, 0, &transferred);
+    if (transferred != -1)
+        p_conn->worker->s->transferred += transferred;
+    status = ap_pass_brigade(origin->output_filters, bb);
+    if (status != APR_SUCCESS) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "AH01084: "
+                      "pass request body failed to %pI (%s)",
+                      p_conn->addr, p_conn->hostname);
+        if (origin->aborted) {
+            const char *ssl_note;
+
+            if (((ssl_note = apr_table_get(origin->notes, "SSL_connect_rv"))
+                 != NULL) && (strcmp(ssl_note, "err") == 0)) {
+                return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR,
+                                     "Error during SSL Handshake with"
+                                     " remote server");
+            }
+            return APR_STATUS_IS_TIMEUP(status) ? HTTP_GATEWAY_TIME_OUT : HTTP_BAD_GATEWAY;
+        }
+        else {
+            return HTTP_BAD_REQUEST;
+        }
+    }
+    apr_brigade_cleanup(bb);
+    return OK;
+}

See the documentation for the actual process.

01

01 2013

Online Speech Recognition for Dictation

I’m working on the integration of kiku (voice recognition to control your OS) to Vinux (Linux for the visually impaired). For now the feedback about kiku is great, but one thing people wants is a solution for dictation (that is, speech to text). kiku could be use for that task, but sadly there is no good (accurate) acoustic model available under GPL (Voxforge is a good start, but not there yet – please contribute).

So i came up with this online speech recognition for dictation solution to overcome this limitation. Speak in different languages, spell check, translate and text to speech all in one place. You’ll need Google Chrome and a microphone.

16

06 2011

kohana 3.2 tutorial

Kohana is discontinued. I’m now using PhalconPHP / Laravel, but any framework supported by HHVM is a good choice. RIP Kohana, it was nice to do business with you…


Below you will find an article / tutorial on Kohana 3.2 – An elegant HMVC PHP5 framework that provides a rich set of components for building web applications.

I am sharing a site template that use authentication & internationalization. You can download it from github. Any pull request will be more than welcome!

Download via Github:
https://github.com/patricksebastien/kohana-3.2-example


Topics



Install

Using GIT:
http://kohanaframework.org/3.2/guide/kohana/tutorials/git
or
Download:
http://kohanaframework.org/download


Structure of folders:
www/yoursite/site/index.php & .htaccess -> and your assets (css, images, js)
www/yoursite/application -> the very core of your site
www/yoursite/module & system -> core of kohana
or:
www/kohana/version/system & module -> multiple site using kohana
or:
everything in www/yoursite/site (application, module, system, index.php, .htaccess, …)


Test:
127.0.0.1/
If it’s greenish, then remove install.php


index.php
Reflect this structure in www/yoursite/site/index.php:

1
2
3
$application = '../application';
$modules = '../modules';
$system = '../system';

bootstrap.php
Modify application/bootstrap.php

1
date_default_timezone_set('America/Montreal');

Set PRODUCTION vs DEVELOPMENT

1
2
3
4
5
if (isset($_SERVER['KOHANA_ENV'])) {
    Kohana::$environment = constant('Kohana::'.strtoupper($_SERVER['KOHANA_ENV']));
} else {
    Kohana::$environment = ($_SERVER['REMOTE_ADDR'] == '127.0.0.1' ? Kohana::DEVELOPMENT : Kohana::PRODUCTION);
}

Initialize Kohana

1
2
3
4
5
'base_url'   => '/', // or for example: /yoursite/site/mykoapp
'index_file' => FALSE, // SEO (avoid index.php/mycontroller/action)
'profile' => (Kohana::$environment !== Kohana::PRODUCTION), //see how good you are
'caching' => (Kohana::$environment === Kohana::PRODUCTION),
'errors' => TRUE, //for custom 404, 500 FALSE for internal error handling

Enable modules (for example):

1
2
3
'auth'       => MODPATH.'auth',       // Basic authentication
'database'   => MODPATH.'database',   // Database access
'orm'        => MODPATH.'orm',        // Object Relationship Mapping

Many modules are available:
https://github.com/kolanos/kohana-universe
http://kohana-modules.com/

Set the routes (default controller will be login.php in this example)
http://kohanaframework.org/3.2/guide/kohana/routing

The order of your routes are important!

1
2
3
4
5
Route::set('default', '(<controller>(/<action>(/<id>)))')
    ->defaults(array(
        'controller' => 'login', // application/classes/controller/login.php
        'action'     => 'index',
    ));

.htaccess
Add this line at the very top of .htaccess (protect from sniffing directory)

1
Options All -Indexes -Multiviews

and modify (/ or for example: /yoursite/site/mykoapp)

1
2
# Installation directory
RewriteBase /

 
Template
http://kerkness.ca/kowiki/doku.php?id=template-site:create_the_template

Create the template controller (classes/controller/template/website.php) extending Controller_Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Template_Website extends Controller_Template
{
    public $template = 'template/website';

    /**
     * The before() method is called before your controller action.
     * In our template controller we override this method so that we can
     * set up default values. These variables are then available to our
     * controllers if they need to be modified.
     */

    public function before() {
        parent::before();
        if ($this->auto_render) {
            // Initialize empty values
            $this->template->title   = '';
            $this->template->content = '';
            $this->template->styles = array();
            $this->template->scripts = array();
        }
    }
     
    /**
     * The after() method is called after your controller action.
     * In our template controller we override this method so that we can
     * make any last minute modifications to the template before anything
     * is rendered.
     */

    public function after() {
        if ($this->auto_render) {
            $styles = array(
            'assets/css/website.css' => 'screen, projection',
            );
            $scripts = array(
            'http://code.jquery.com/jquery.min.js',
            );
            $this->template->styles = array_merge( $this->template->styles, $styles );
            $this->template->scripts = array_merge( $this->template->scripts, $scripts );
        }
        parent::after();
    }
}

Create the controller (classes/controller/login.php) extending Controller_Template_Website (look at the second action for an example on how to use another template per action):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Controller_Login extends Controller_Template_Website {

    public function action_index()
    {
        $this->template->title = 'Log in';
        $this->template->content = View::factory('login'); // application/views/login.php
    }

        // this action is using another template but using the same Controller_Template_Website
    public function action_showinfooverlay()
    {
        $this->template = 'template/overlay';
        parent::before();
        $this->template->title = 'Log in';
        $this->template->content = View::factory('login'); // application/views/login.php
    }
}

Create the html template (view/template/website.php)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo substr(I18n::$lang, 0, 2); ?>" lang="<?php echo substr(I18n::$lang, 0, 2); ?>">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="language" content="<?php echo I18n::$lang ?>" />
<title><?php echo $title ?></title>
<?php foreach ($styles as $file => $type) echo HTML::style($file, array('media' => $type)), PHP_EOL ?>
<?php foreach ($scripts as $file) echo HTML::script($file), PHP_EOL ?>
</head>
<body>
<div id="wrapper">
<?php echo $content ?>
</div>
</body>
</html>

Finally create your view content application/views/login.php

1
2
3
4
5
6
7
8
9
<?php echo Form::open(); ?>
<dl>
    <dt><?php echo Form::label('username', 'User') ?></dt>
    <dd><?php echo Form::input('username') ?></dd>
    <dt><?php echo Form::label('password', 'Pwd') ?></dt>
    <dd><?php echo Form::password('password') ?></dd>
</dl>
<p><?php echo Form::submit(NULL, 'Log in'); ?></p>
<?php echo Form::close(); ?>

At this point you can point your browser to see the login page:
http://localhost/ -> depending on base_url and .htaccess (could be in a sub-folder)
http://localhost/login -> not defined login as the default controller in bootstrap.php

Someone on #kohana (irc / freenode) made a suggestion of using view classes instead of template controller. Here’s two solutions: https://github.com/zombor/kostache & https://github.com/beautiful/view

 
Configure
Database
Copy modules/database/config/database.php to application/config/database.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'default' => array
(
    'type'       => 'mysql',
    'connection' => array(
        'hostname'   => 'localhost',
        'database'   => 'yourdb',
        'username'   => 'user',
        'password'   => 'pwd',
        'persistent' => FALSE,
    ),
    'table_prefix' => '',
    'charset'      => 'utf8',
    'caching'      => FALSE,
    'profiling'    => FALSE, // if you use profiling turn this on (to see querys)
),

Cookie
in application/bootstrap.php
must be after: spl_autoload_register(array(‘Kohana’, ‘auto_load’));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * Cookie
 */

// Set the magic salt to add to a cookie
Cookie::$salt = 'fjsdijeihrewhbfsugfuyegwufewgwb';
// Set the number of seconds before a cookie expires
Cookie::$expiration = DATE::WEEK; // by default until the browser close
// Restrict the path that the cookie is available to
//Cookie::$path = '/';
// Restrict the domain that the cookie is available to
//Cookie::$domain = 'www.mydomain.com';
// Only transmit cookies over secure connections
//Cookie::$secure = TRUE;
// Only transmit cookies over HTTP, disabling Javascript access
//Cookie::$httponly = TRUE;

Session (stored in database)
http://kohanaframework.org/3.2/guide/kohana/sessions
in application/bootstrap.php add the default session handler:

1
Session::$default = 'database';

Copy system/config/encrypt.php to application/config/encrypt.php

1
2
3
4
5
6
7
8
9
return array(

    'default' => array(
        'key'   => 'fjdsjkfdskjfurew',
        'cipher' => MCRYPT_RIJNDAEL_128,
        'mode'   => MCRYPT_MODE_NOFB,
    ),

);

Create a table if you want to use database session

1
2
3
4
5
6
7
CREATE TABLE  `sessions` (
            `session_id` VARCHAR(24) NOT NULL,
            `last_active` INT UNSIGNED NOT NULL,
            `contents` TEXT NOT NULL,
        PRIMARY KEY (`session_id`),
        INDEX (`last_active`)
        ) ENGINE = MYISAM;

Copy system/config/session.php to application/config/session.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
return array(
    'database' => array(
        'name' => 'session',
        'encrypted' => TRUE, // need a key in config/encrypt.php
        'lifetime' => DATE::HOUR, // 0 = expire when the browser close
        'group' => 'default',
        'table' => 'sessions',
        'columns' => array(
            'session_id'  => 'session_id',
            'last_active' => 'last_active',
            'contents'    => 'contents'
        ),
        'gc' => 500,
    ),
);

Use it in your controller:

1
2
Session::instance()->set('key', 'value');
Session::instance()->get('key');

yoursite
Create a file in application/config/yoursite.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php defined('SYSPATH') or die('No direct access allowed.');
return array(
    'myconfig1' => 'fjdsjkfdskjfurew',
       'playlists' => array
    (
        1478363 => 10171,
        22857234 => 10171,
        386 => 10171,
        722 => 10171,
        893 => 10171,
        237 => 10171,
    ),
   
);

Then you can call a config like this:

1
2
$playlists = Kohana::$config->load('yoursite.playlists');
Kohana::$config->load('yoursite.myconfig1 ');

Message
(might be better to use I18N directly)

Create a file in application/message/yoursite.php for you project

1
2
3
4
5
<?php defined('SYSPATH') or die('No direct script access.');
return array(
    'permission'         => 'You don\'t have the permission',
    'wrong'         => 'Wrong username or password',
);

Then you can use it like this:

1
Kohana::message('yoursite', 'permission');

Translation
http://blog.mixu.net/2010/11/11/kohana-3-i18n-tutorial/

1
<?php echo __('Dear :firstname, your username is: :user', array(':firstname' => 'gfdgfdg', ':user' => 'gfdgfd')); ?>

 
Validation
http://kohanaframework.org/3.2/guide/kohana/security/validation
Copy system/messages/validation to application/message/validation.php if you want to change the error message

1
2
3
4
5
6
7
8
9
10
11
12
13
// Validate a form ($_POST)
if (isset($_POST) && Valid::not_empty($_POST)) {       
    // Validate the login form
    $post = Validation::factory($_POST)
    ->rule('username', 'not_empty')
    ->rule('username', 'regex', array(':value', '/^[a-z_.]++$/iD'))
    ->rule('password', 'not_empty')
    ->rule('password', 'min_length', array(':value', 3));
           
    // If the form is valid and the username and password matches
    if ($post->check()) {
        echo 'Validated';
    }

Using a callback for custom validation & error message

1
2
3
4
5
6
7
8
9
// form post handling
if (isset($_POST) && Valid::not_empty($_POST)) {           
    // validate
    $post = Validation::factory($_POST)
    ->rule('username', 'alpha_numeric')
    ->rule('password', array($this, 'pwdneusr'), array(':validation', ':field', 'username'));              
    if ($post->check()) {
        }
}
1
2
3
4
5
6
7
8
9
// CALLBACK
// validation rule: password != username
public function pwdneusr($validation, $password, $username)
{
    if ($validation[$password] === $validation[$username])
    {
        $validation->error($password, 'pwdneusr');
    }
}

 
Error page

http://kohanaframework.org/3.2/guide/kohana/tutorials/error-pages

Create the views
views/error/404.php / 500.php etc…

Extend the exception handler of Kohana
classes/kohana/exception.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php defined('SYSPATH') or die('No direct script access.');
class Kohana_Exception extends Kohana_Kohana_Exception {
    public static function handler(Exception $e)
    {
        if (Kohana::DEVELOPMENT === Kohana::$environment)
        {
            parent::handler($e);
        }
        else
        {
            try
            {
                Kohana::$log->add(Log::ERROR, parent::text($e));
 
                $attributes = array
                (
                    'controller' => 'error',
                    'action'  => 500,
                    'message' => rawurlencode($e->getMessage())
                );
 
                if ($e instanceof HTTP_Exception)
                {
                    $attributes['action'] = $e->getCode();
                }
 
                // Error sub-request.
                echo Request::factory(Route::get('error')->uri($attributes))
                ->execute()
                ->send_headers()
                ->body();
            }
            catch (Exception $e)
            {
                // Clean the output buffer if one exists
                ob_get_level() and ob_clean();
 
                // Display the exception text
                echo parent::text($e);
 
                // Exit with an error status
                exit(1);
            }
        }
    }
}

Create the controller:
classes/controller/error.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Error extends Controller_Template_Nobrand {
    public function before()
    {
        parent::before();
        // Internal request only!
        if (Request::$initial !== Request::$current) {
            if ($message = rawurldecode($this->request->param('message'))) {
                $this->template->message = $message;
            }
        } else {
            $this->request->action(404);
        }
        $this->response->status((int) $this->request->action());
    }
    public function action_404()
    {
        $this->template->title = '404 Not Found';
        $this->template->content = View::factory('error/404' );
    }
    public function action_500()
    {
        $this->template->title = 'Internal Server Error';
        $this->template->content = View::factory('error/500' );
    }
    public function action_503()
    {
        $this->template->title = 'Maintenance Mode';
        $this->template->content = View::factory('error/503' );
    }
}

Edit application/bootstrap.php to add the route:

1
2
3
4
Route::set('error', 'error/<action>(/<message>)', array('action' => '[0-9]++', 'message' => '.+'))
    ->defaults(array(
        'controller' => 'error'
    ));

 
Authentication
Copy modules/auth/config/auth.php to application/config/auth.php

1
2
3
4
5
6
7
return array(
    'driver'       => 'orm',
    'hash_method'  => 'sha256',
    'hash_key'     => 'wigbble',
    'lifetime'     => Date::HOUR * 2,
    'session_key'  => 'auth_user',
);

Schema for mysql / postgresql located:
modules/orm/auth-schema-mysql.sql

Change the rules if you don’t want to required an email (you will also need to remove the index in mysql: uniq_email – BTREE)
Copy modules/orm/classes/model/auth/user.php to application/classes/model/auth/user.php and change the public function rules() to your needs

It’s a good idea to add a new role for your normal user, that way you can list them easily:

1
$p = ORM::factory('role', array('name' => 'participant'))->users->find_all();

Create application/messages/models/user.php

1
2
3
4
5
<?php defined('SYSPATH') or die('No direct script access.');
return array(
    'username.unique'         => 'Username must be unique',
    'email.unique'         => 'Email must be unique',
);

If you want to use the remember feature:

1
2
$remember = isset($post['remember']);
Auth::instance()->login($post['username'], $post['password'], $remember)

then you need to be sure to have a cookie salt in application/bootstrap.php

1
2
3
4
/**
* Cookie salt for remember user info
*/

Cookie::$salt = 'fdsh-tretgd-re-gfds-gt-erg-fdg-';

Some useful stuff:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Add an administrator (in a temporary controller)
$model = ORM::factory('user');
$model->values(array(
    'username' => 'admin',
    'password' => 'admin',
    'password_confirm' => 'admin',
        'email' => 'your@email.com',
));
$model->save();
// remember to add the login role AND the admin role
// add a role; add() executes the query immediately
$model->add('roles', ORM::factory('role')->where('name', '=', 'login')->find());
$model->add('roles', ORM::factory('role')->where('name', '=', 'admin')->find());
1
2
3
4
5
6
7
8
9
// If this user doesn't have the admin role, and is not trying to login, redirect to login
public function before()
{
  parent::before();
  if ( ! Auth::instance()->logged_in('admin') AND Request::current()->uri() !== 'manage')
  {
    $this->request->redirect('/manage');
  }
}
1
2
3
4
// Administrator already logged in, redirect to dashboard
if (Auth::instance()->logged_in('admin')) {
    $this->request->redirect('manage/dashboard');
}
1
2
// Log the user
Auth::instance()->login($post['username'], $post['password'], FALSE)
1
2
// Check if the user have the admin permission
if(!Auth::instance()->logged_in('admin')) {
1
2
// Log user out
Auth::instance()->logout();
1
2
3
4
// check if email or username (automagic) is already taken
if(ORM::factory('user')->unique_key_exists($_POST['username'])) {
       echo "FOUND";
}

 
ORM
http://kohanaframework.org/3.2/guide/orm/
http://karlsheen.com/kohana/kohana-3-orm-tutorial-and-samples/
http://kohanaframework.blogspot.com/2010/12/kohana-3-orm-simple-example.html
http://www.geekgumbo.com/2011/05/24/kohana-3-orm-a-working-example/
http://kohanaframework.org/3.2/guide/api/ORM

ORM is included with the Kohana 3.x install but needs to be enabled before you can use it. In your application/bootstrap.php file modify the call to Kohana::modules and include the ORM modules:

1
'orm' => MODPATH.'orm',

The table name must be in plural;
The table must have an id with auto increment (required);
You must create a Model that extends ORM class (this one not in plural)

1)
Create a table with a “s” as the end:

tracking -> trackings
category -> categories

2)
Create a model (application/classes/model) without the “s”

tracking.php
category.php

1
2
3
4
5
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_Tracking extends ORM
{
    ...
}
1
2
3
4
5
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_Category extends ORM
{
    ...
}

3)
Establish your relation (one-to-one, one-to-many etc…)
http://kohanaframework.org/3.2/guide/orm/relationships

4)
Use your model / ORM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try
{
    $tracking = ORM::factory('tracking');
    $tracking->user_id = Auth::instance()->get_user()->id;
    $tracking->session_id = Session::instance()->id();
    $tracking->title = ' fdsfdsf ';
    if($tracking->save()) {
        echo "save";
    } else {
        echo "for some reason, there's an error";
    }
  }
catch (ORM_Validation_Exception $e) {
    echo "error";
    var_dump($e->errors());
}

Some useful stuff:

1
2
3
$user = ORM::factory('user');
echo $user->count_all();
echo $user->last_query();
1
2
3
4
$playlists = ORM::factory('playlist')->where('week_id', '=', 1)->find_all();
foreach ($playlists as $playlist) {
    echo $playlist->url;
}

Last ID from ->save();

1
2
$myormmodel->save();
echo $myormmodel->id();

Dynamic ORM query builder

1
2
3
4
5
6
7
8
public function search_keywords(array $keywords)
    {
            foreach($keywords as $keyword)
            {
                    $this->or_where('title', 'like', '%'.$keyword.'%');
            }
            return $this->find_all();
    }

ORM Validation
http://kohanaframework.org/3.2/guide/orm/examples/validation
http://kohanaframework.org/3.2/guide/kohana/security/validation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_Member extends ORM {
    public function rules()
    {
        return array(
            'username' => array(
                array('not_empty'),
                array('min_length', array(':value', 4)),
                array('max_length', array(':value', 32)),
                array(array($this, 'username_available')),
            ),
            'password' => array(
                array('not_empty'),
            ),
        );
    }
    public function filters()
    {
        return array(
            'password' => array(
                array(array($this, 'hash_password')),
            ),
        );
    }
    public function username_available($username)
    {
        // There are simpler ways to do this, but I will use ORM for the sake of the example
        return ORM::factory('member', array('username' => $username))->loaded();
    }
 
    public function hash_password($password)
    {
        // Do something to hash the password
    }
}

 
Database:
http://kohanaframework.org/3.2/guide/database/
http://kohanaframework.org/3.2/guide/api/Database

There’s 2 ways to query a database: prepared (normal SQL) and query builder (dynamic)

Prepared:

1
2
3
4
5
6
$query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user AND status = :status');
 
$query->parameters(array(
    ':user' => 'john',
    ':status' => 'active',
));

Query builder:

1
2
3
4
$query = DB::select()->from('users')->where('username', '=', 'john');
$query = DB::select('username')->distinct(TRUE)->from('posts');
$query = DB::select()->from(`posts`)->limit(10)->offset(30);

Results:

1
2
3
4
5
6
$results = DB::select()->from('users')->where('verified', '=', 0)->execute();
foreach($results as $user)
{
    // Send reminder email to $user['email']
    echo $user['email']." needs to verify his/her account\n";
}
1
2
3
4
5
6
$results = DB::select()->from('users')->where('verified', '=', 0)->as_object()->execute();
foreach($results as $user)
{
    // Send reminder email to $user->email
    echo $user->email." needs to verify his/her account\n";
}

Only get 1 result:

1
$total_users = DB::select(array('COUNT("username")', 'total_users'))->from('users')->execute()->get('total_users', 0);
1
2
3
// Get the total number of records in the "users" table
$db = Database::instance();
$count = $db->count_records('testi');

 
3rd party libraries
The convention is to place 3rd party files in application/vendor. For instance, if you had an installation of Doctrine, you would place it in application/vendor/doctrine.

1
2
require Kohana::find_file('vendor', 'Swift-4.0.5/lib/swift_required');
$transport = Swift_SmtpTransport::newInstance(...); // This is autoloaded for me by Swiftmailer

PHPExcel – create PDF, CSV, Excel:
application/vendor/phpexcel/PHPExcel.php & PHPExcel
then in your controller:

1
2
require Kohana::find_file('vendor', 'phpexcel/PHPExcel');
$objPHPExcel = new PHPExcel();

Email – you can use this module:
https://github.com/Luwe/Kohana-Email
or directly use swiftmailer:
http://swiftmailer.org/

1
2
3
4
5
6
7
8
9
10
11
12
require Kohana::find_file('vendor', 'swift/swift_required');
//Create the Transport
$transport = Swift_SmtpTransport::newInstance('localhost', 25);
//Create the Mailer using your created Transport
$mailer = Swift_Mailer::newInstance($transport);
//Create a message
$message = Swift_Message::newInstance('Email')
->setFrom(array('from@email.net' => 'From'))
->setTo(array('to@email.net'))
->setBody('An email');
//Send the message
$result = $mailer->send($message);

 
Helper
http://kohanaframework.org/3.2/guide/api/Arr
If you want to add some custom helper (generally used statically) or library (instantiated / object), for example: application/classes/participants.php

1
2
3
4
5
<?php defined('SYSPATH') or die('No direct script access.');
class Participant {
    static function currentweek() {
    }
}

in your controller:

1
$weektodisplay = Participant::currentweek();

 
Tips
To get the params in a controller (depending on your routes in application/bootstrap.php)

1
$this->request->param('id')

Debug:

1
echo Debug::vars();

To get the current controller:

1
echo Request::current()->uri();

To point at the right directory use:

1
<?php echo URL::base(); ?>

To get a custom column from users (auth):

1
echo Auth::instance()->get_user()->week;

Date:

1
2
Date::formatted_time('now', 'm-d-Y');
//constant Date::WEEK Date::YEAR

Way to use the model and the post for edition in form:

1
2
3
4
5
// Received the POST
if (isset($_POST) && Valid::not_empty($_POST)) {
    // keep but not saved
    $participant->values($_POST, array('email', 'username','password'))
}

To redirect use:

1
$this->request->redirect('manage/dashboard');

To make a link use:

1
<?php echo HTML::anchor('playlist', 'Playlist', array('style' => 'color: #FFF')); ?>

Function inside controller:
inside action_x():

1
array_walk($trackings, array($this, '_replaceplaylistendtime'), $params);

outside action_x():

1
2
3
static function _replaceplaylistendtime(&$value, $key, $p) {
    ...
}

 
Jquery
Use the latest minified version on google server:
http://code.jquery.com/jquery.min.js

User interface:
http://ninjaui.com/
http://jqueryui.com/
http://flowplayer.org/tools/index.html

Ajax:
In your “ajax” controller / action

1
2
3
4
5
if ($this->request->is_ajax()) {
    $id = json_decode($_POST['refresh']);
    $this->auto_render = FALSE;
    echo json_encode(array('result' => $id));
}

In your jquery:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$.ajax({
                type: "POST",
                url: "<?php echo URL::base(); ?>home/refresh",
                data: "refresh=1",
                async: true,
                dataType: "json",
                success: function(resultArray, textStatus, XMLHttpRequest)
                {
                    var result = parseInt(resultArray['result']);
                    alert(result);
                },
                error: function(request, textStatus, errorThrown)
                {
                    alert('error refreshing the session');
                }
});

 
Bookmarks
http://kohanaframework.org/3.2/guide/kohana/tutorials/
http://kohanaframework.org/3.2/guide/api or http://kohana.nerdblog.pl/api/

 
Deploying:
See this for more information about deploying Kohana application
http://nerdblog.pl/2011/09/05/deploying-kohana-3-2-application-in-production/

04

04 2010