13 September 2014, Saturday
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.
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
$ 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
$ 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
$ 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
$ 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
$ 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.
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");
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;
}
$ 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"}]}
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>