Lean web services using C, FastCGI and MySQL

September 13, 2014

I like the C programming language. What I like most is that with C you have to be very explicit about what you would like the computer to do. Consequently, there are fewer surprises compared to other programming languages that I have used. I also like the fact that I can recall most of the syntax and semantics of the language from the top-of-my-head. Finally, if something goes wrong, I like the certainty of being able to debug and pinpoint the cause of failure by simply going through the source code. So, it got me thinking: what if I wanted to write lean web services using C? These are my notes for future reference.

I have used simpler modern frameworks that are based on Java, PHP and JSP. They are easy to develop with initially but debugging and finding out performance bottlenecks or memory leaks is usually quite a hassle because there are so many components interacting at run-time at different levels-of-abstraction. I also find that as the complexity of the framework grows, the log details become more cryptic and less helpful. Furthermore, all of these frameworks can be very resource intensive. If, say, I wished to replicate several server nodes for scalability on Amazon EC2, the costs of maintaining several resource hungry applications can be extremely prohibitive. I would have to choose nodes that are sufficiently geared to handle the resource demands, which are more expensive to run. So, the question is, is there a way I could replicate the same functionalities and scalability using a much leaner framework, a framework that is less resource intensive. This got me interested to learn more about the older technologies, which existed when our servers were not very powerful.

Before I proceed, I wish to apologise for the lack of quantitative performance metrics to justify my claims. Perhaps, as I learn more, I should be able to gather evidence to support or refute my hypothesis. For now, these are merely hunches based on personal experience.

With FastCGI, I am hoping that the web services could be simple light-weight processes, and hence scaling these with cheaper server nodes would bring the cost significantly down. The cost of initially developing the application would inevitably be higher (if this was not the case, why would anyone invent simpler frameworks in the first place); however, once deployed, the long-term pay-off of running and maintaining a lean cost-effective application is a serious proposition. After all, the costs of running the servers are recurring and surely must affect the profit margin.

Based on my experience developing and using RESTful web services, I have learned that the key is in defining a clear and effective application programming interface (API), with which the client-side JavaScript code can interact efficiently. Once the APIs are well defined, I find that writing the web services is not that challenging. I am hoping that the same would be the case when using C and FastCGI, albeit with a bit more programming, which I do not mind. With more computational power becoming available at the hands of the customers, it would be prudent to leverage this for a much leaner server framework that are tuned perfectly to collecting and serving data as fast and cheaply as possible. I hope that this turns out to be an interesting journey for me.

I studied several blogs and articles to learn FastCGI and how it can be made to work with Apache, MySQL and C. I have listed these in the References section. All I did here is consolidate the main lessons based on my hands-on experience. While the examples I have presented are quite simple, I wanted to get something up-and-running as soon as I could. I hope that you'll find this note useful.

Create the workspace

In this note, I am using Mac OSX 10.9.4 for development. We shall do all of our work inside the ~/fastcgi_learn directory.

$ mkdir -p ~/fastcgi_learn
$ mkdir -p ~/fastcgi_learn/lib
$ mkdir -p ~/fastcgi_learn/mods
$ mkdir -p ~/fastcgi_learn/progs

Installing FastCGI library

$ cd ~/fastcgi_learn/lib
$ curl -o fcgi.tar.gz http://www.fastcgi.com/dist/fcgi.tar.gz
$ tar xvozf fcgi.tar.gz
$ cd fcgi-2.4.1-SNAP-0311112127/
$ ./configure
$ make
$ sudo make install

Installing Apache mods for FastCGI

$ cd ~/fastcgi_learn/mods
$ curl -o mod_fastcgi-current.tar.gz \
  http://www.fastcgi.com/dist/mod_fastcgi-current.tar.gz
$ tar xvozf mod_fastcgi-current.tar.gz
$ cd mod_fastcgi-2.4.6/
$ sudo apxs -n mod_fastcgi -i -a -c mod_fastcgi.c fcgi_buf.c fcgi_config.c \
  fcgi_pm.c fcgi_protocol.c fcgi_util.c

If you get the "apxs:Error: Command failed with rc=65536" error during FastCGI mod compilation, the fix is as follows:

$ sudo ln -s \
  /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/ \
  /Applications/Xcode.app/Contents/Developer/Toolchains/OSX10.9.xctoolchain

Set up FastCGI mod

$ sudo emacs -nw /private/etc/apache2/httpd.conf

Replace

LoadModule mod_fastcgi_module libexec/apache2/mod_fastcgi.so

with

LoadModule fastcgi_module libexec/apache2/mod_fastcgi.so

At the end of file, add:

<IfModule mod_fastcgi.c>
        FastCgiIpcDir /tmp/fcgi_ipc/
        AddHandler fastcgi-script .fcgi
</IfModule>

Create the web server communication channel

$ sudo mkdir /tmp/fcgi_ipc
$ sudo chmod 777 /tmp/fcgi_ipc
$ sudo apachectl restart
$ sudo apachectl -t

Compile an example

$ cd ~/fastcgi_learn/progs
$ emacs -nw counter.c

Type in the following example code:

#include <fcgi_stdio.h>
#include <stdlib.h>
int main(void)
{
    int count = 0;
    while(FCGI_Accept() >= 0)
        printf("Content-type: text/html\r\n\r\n"
               "Request number %d on host <i>%s</i>\n",
               ++count, getenv("SERVER_NAME"));
    return 0;
}

Compile the program

$ gcc -o counter.fcgi counter.c -lfcgi

Deploy application

$ sudo cp counter.fcgi /Library/WebServer/CGI-Executables/counter.fcgi
$ sudo apachectl restart

Check if it is working http://127.0.0.1/cgi-bin/counter.fcgi. Refreshing the browser should increment the counter.

Example MySQL application

Create the database

create database fastcgi_test;
use fastcgi_test;
create table fruits (
    id int not null auto_increment,
    name varchar(64) not null,
    primary key (id)
);
grant all on fastcgi_test.* to 'testuser'@'localhost' identified by 'testuser';
flush privileges;
insert into fruits (name) values ("Apple"), ("Orange"), ("Banana");

The application source

To keep the source code small, we are forgoing error checks.

#include <fcgi_stdio.h>
#include <mysql.h>
#include <stdlib.h>

int main(void)
{
    MYSQL *con = mysql_init(NULL);
    mysql_real_connect(con, "localhost", "testuser", "testuser",
                       "fastcgi_test", 0, NULL, 0);

    while(FCGI_Accept() >= 0) {
            printf("Content-type: application/json\r\n\r\n");
            mysql_query(con, "SELECT * FROM fruits");
            MYSQL_RES *result = mysql_store_result(con);
            int num_fields = mysql_num_fields(result);
            MYSQL_ROW row;
            printf("{\"names\":[");
            int count = 0;
            while ((row = mysql_fetch_row(result))) {
                    printf("%s{", count++ ? "," : "");
                    for (int i = 0; i < num_fields;){
                            printf("\"%d\":\"%s\"", i, row[i] ? row[i] : "");
                            if (++i < num_fields)
                                    printf(",");
                    }
                    printf("}");
            }
            printf("]}");
            mysql_free_result(result);
    }

    mysql_close(con);
    mysql_library_end();
    return 0;
}

Compilation and deployment

$ gcc -o example example.c -lfcgi -I /usr/local/mysql/include/ \
  -L /usr/local/mysql/lib/ -lmysqlclient -lm -lz
$ sudo cp example /Library/WebServer/CGI-Executables/example.fcgi
$ sudo apachectl restart

If you visit http://127.0.0.1/cgi-bin/example.fcgi, you should see:

{"names":[{"0":"1","1":"Apple"},{"0":"2","1":"Orange"},{"0":"3","1":"Banana"}]}

Dynamic linking fix

If you get Library not loaded: libmysqlclient.16.dylib error when running example, the fix is as follows. The following will give the current incorrect library path:

$ otool -DX /usr/local/mysql-5.5.24-osx10.6-x86_64/lib/libmysqlclient.dylib
libmysqlclient.18.dylib

You run the following to fix the path:

$ sudo install_name_tool -id /usr/local/mysql/lib/libmysqlclient.18.dylib \
  /usr/local/mysql/lib/libmysqlclient.dylib

You should now see the corrected path:

$ otool -DX /usr/local/mysql-5.5.24-osx10.6-x86_64/lib/libmysqlclient.dylib
/usr/local/mysql/lib/libmysqlclient.18.dylib

UPDATE

On 3 January 2015, I noticed that after upgrading to OSX 10.10 (Yosemite), the FastCGI modules from http://www.fastcgi.com/ no longer compiles. This is to do with Apache upgrade from version 2.2 to 2.4, which changed the APIs. I tried to check if there is a newer module available; unfortunately, the FastCGI web-site was down. As an alternative, you could use Apache's own FastCGI implementation named mod_fcgid. If you have Homebrew installed, you can do the following to install Apache's mod_fcgid FastCGI module:

$ brew install homebrew/apache/mod_fcgid

Now edit the Apache config as follows:

$ sudo emacs -nw /private/etc/apache2/httpd.conf

Add following, using path as advised at the end of the above brew command.

LoadModule fcgid_module /usr/local/Cellar/mod_fcgid/2.3.9/libexec/mod_fcgid.so

At the end of file, add:

<IfModule fcgid_module>
        AddHandler fcgid-script .fcgi
</IfModule>

References

comments powered by Disqus