前几天在找 Android 应用的时候,又见到了 QR code。说到 QR code,记得2006年,我还在维基活跃的时候,有一个人在维基的 QQ 群上发了这个的东西,然后大家就觉得很好玩。很早的事情了。寒假回福州的时候,在乌山公园,不少地方也有这个二维码,是移动设置的“物联网试验区”。
二维码着实是一个很有趣的东西。据我去过日本的同学讲,这个东西在日本已经十分普及了。在维基上查了一下,QR code 是开放的,有专利但没有被行使。而且识别算法似乎已经十分稳定成熟了。Android 手机里面安装一个 Barcode Scanner 就可以利用摄像头扫描二维码了,非常方便~
二维码可以干嘛呢?它可以储存网址、储存电话、储存名片信息,扫描一下就可以读取出来,将人从人工的复制方式中解脱出来~
于是我便也做了一个 CGI 的 QR code 生成器。
这个生成器用了开源的 libqrencode 和 libmagick 这两个库,都可以直接在 Ubuntu 里面 apt-get 下来,第一个是生成二维码数据,第二个是输出为图片。使用二进制 CGI 还涉及了我可爱的服务器商的支持问题等等……
生成器源代码
核心程序没什么好说的,其实就是查文档的问题而已。参考了一下 libqrencode 和 libmagick 的文档就可以了。
直接贴代码:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <qrencode.h> #include <magick/api.h> #define DEFAULT_TEXT "http:/""/upsuper.org/" int main(int argc, char **argv) { int i, j; int scale; QRecLevel level; char *text; // 设置默认参数 scale = 8; level = QR_ECLEVEL_Q; text = (char *)malloc(strlen(DEFAULT_TEXT) + 1); strcpy(text, DEFAULT_TEXT); // 读取参数 const char *orig; char *query_string; char *p; int len; orig = getenv("QUERY_STRING"); len = strlen(orig); query_string = (char *)malloc(len + 2); *query_string = '&'; strcpy(query_string + 1, orig); for (p = query_string; *p; ++p) { if (*p != '&') continue; if (strncasecmp(p + 1, "scale=", 6) == 0) { // 读取缩放级 scale = atol(p + 7); } else if (strncasecmp(p + 1, "text=", 5) == 0) { // 读取文本 char *p_last, *p_src, *p_dest; for (p_last = p + 6; *p_last && *p_last != '&'; ++p_last); text = (char *)realloc((void *)text, p_last - p - 6 + 1); // urldecode for (p_src = p + 6, p_dest = text; p_src < p_last; ++p_src, ++p_dest) { if (*p_src == '+') { *p_dest = ' '; } else if (*p_src == '%') { int code; if (sscanf(p_src + 1, "%2x", &code) != 1) code = '?'; *p_dest = code; p_src += 2; } else { *p_dest = *p_src; } } *p_dest = '\0'; } else if (strncasecmp(p + 1, "level=", 6) == 0) { // 读取纠错级别 switch (*(p + 7)) { case 'l': case 'L': level = QR_ECLEVEL_L; break; case 'm': case 'M': level = QR_ECLEVEL_M; break; case 'q': case 'Q': level = QR_ECLEVEL_Q; break; case 'h': case 'H': level = QR_ECLEVEL_H; break; } } } // 生成 QR code QRcode *qrcode; int width; qrcode = QRcode_encodeString8bit(text, 0, level); width = qrcode->width; // 初始化 Image Magick ImageInfo *image_info; ExceptionInfo exception; InitializeMagick(*argv); GetExceptionInfo(&exception); // 初始化图片 Image *image, *resized_image; image_info = CloneImageInfo((ImageInfo *)NULL); image = AllocateImage(image_info); image->columns = image->rows = width; // 生成图片 PixelPacket *pixel_packet; int t_bit; for (i = 0; i < width; ++i) { pixel_packet = SetImagePixels(image, 0, i, width, 1); if (! pixel_packet) break; for (j = 0; j < width; ++j, ++pixel_packet) { t_bit = ! (qrcode->data[i * width + j] & 1); pixel_packet->red = MaxRGB * t_bit; pixel_packet->green = MaxRGB * t_bit; pixel_packet->blue = MaxRGB * t_bit; } if (! SyncImagePixels(image)) break; } QRcode_free(qrcode); // 处理图片 resized_image = ScaleImage(image, width * scale, width * scale, &exception); DestroyImage(image); CompressImageColormap(resized_image); // 输出图片 puts("Content-Type: image/png"); puts(""); strcpy(resized_image->filename, "png:-"); WriteImage(image_info, resized_image); // 清理 DestroyImage(resized_image); DestroyImageInfo(image_info); DestroyExceptionInfo(&exception); DestroyMagick(); return 0; } |
话说最近喜欢用 C,C++ 什么的最讨厌了……
另外写了个 Makefile:
1 2 3 4 5 | LDFLAGS:=-lqrencode `Magick-config --ldflags --libs` CFLAGS:=`Magick-config --cflags` all: qrcodetest qrcodetest: qrcodetest.c |
下面就是把这个 cgi 部署到服务器的问题了……
部署到服务器
我的这个空间商吧,我猜他装了 libmagick 的库,不过肯定没装开发库,也就是 libmagick-dev 这种东西,这样就编译不了了,比较麻烦。探究了一下发现安装了 gcc~很是高兴啊~而且 cgi 支持 bash 的。
这样就好玩了~我在我的访问不到的根目录下建立了一个 usr 文件夹,就已这个作为存在的核心了~
我写了 bash cgi 程序直接从服务器上获取源代码包,在服务器上解压编译 ImageMagick,也就是下面这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/bin/bash export PATH=$PATH:/xxxx/upsuper.org/usr/bin export CPATH=/xxxx/upsuper.org/usr/include export LIBRARY_PATH=/xxxx/upsuper.org/usr/lib echo Content-Type: text/plain echo wget http://downloads.sourceforge.net/project/imagemagick/ImageMagick/00-6.6.1/ImageMagick-6.6.1-5.tar.bz2 mkdir imagick mv ImageMagick-6.6.1-5.tar.bz2 imagick cd imagick tar -jxvf ImageMagick-6.6.1-5.tar.bz2 cd imagick/ImageMagick-6.6.1-5 ./configure --prefix=/xxxx/upsuper.org/usr make make install make clean make distclean |
至于 qrencode,也不过就是把里面的一些东西改动一下就可以了~由于 make 的优美特性,即使中间被中断也可以断点继续,很好很好~
最后编译好以后,再把我的 cgi 代码传上去,编译,通过了。把生成的文件拷贝出来加上 .cgi,却不能运行……
弄了个 bash 脚本上去加 ldd,发现 libqrencode.so 找不到,libmagick 看起来确实是有预装,在 /usr/lib 里面就轻易找到了。不甘心啊,这个东西应该是有办法解决的……
研究了一下,想起那个环境变量 LD_LIBRARY_PATH。我在 bash 的 cgi 里面加上
1 | export LD_LIBRARY_PATH=/xxxx/upsuper.org/usr/lib |
之后,ldd 里面所有库都可以找到了~真好
然后我就想啊,我们可不可以让他自动设置 LD_LIBRARY_PATH 这个环境变量呢?我就查了一下,发现在 .htaccess 里面加入 SetEnv 是可以的。
于是我就
1 | SetEnv LD_LIBRARY_PATH /xxxx/upsuper.org/usr/lib |
结果不行。弄了个 bash cgi:
1 2 3 4 5 | #!/bin/bash echo Content-Type: text/plain echo env |
发现这个东西没有被加入环境变量当中。郁闷……在网上查了半天,没有发现任何人遇到过这种情况。我就想,不会吧,我这么倒霉?
然后我试了一下网上普遍喜欢用 SetEnv 的样例:
1 | SetEnv TZ Asia/Chongqing |
再打开刚才的查环境变量专用 cgi,发现出现了!
这就奇怪了。这说明 Apache 的 env 模块非常正常,但设置的东西却是选择性的进入 cgi。我猜一定是有什么白名单之类的东西。
于是我就跑去问服务商的技术客服……(又要用英语……)
话说这个主机商的技术客服看起来十分繁忙,我每次去都要等很久很久才有人应答。不知道是我的英语描述水平的问题,还是对方无法理解这种高深的问题,他直接告诉我这是我自己代码的问题,跟他没关系……然后居然就直接关断了聊天,我靠,这什么态度!我就囧了……又连了一次,我非常耐心的跟那个客服解释了一遍我想要的效果,他终于明白了!而且非常耐心的帮我解决问题。最后他告诉我,因为服务器使用了 suEXEC 这个模块,对进入 cgi 的环境变量做了一个白名单,所以不行……
唉,不行就不行呗,早知这样,我也不是没有办法的……
在 suexec 的代码里查到了环境变量白名单,发现所有 HTTP_ 和 SSL_ 开头的都是白名单范围的。于是我就写下了这样一个 .htaccess:
1 2 3 4 5 6 7 | RewriteEngine On RewriteCond %{REQUEST_URI} \.cgi$ RewriteRule ^(.*)$ /setenv.sh?cgi=$1 [QSA] RewriteCond %{QUERY_STRING} ^cgi=([^&]+) RewriteRule . - [E=HTTP_CGI:%1] |
与之配套的 setenv.sh:
1 2 3 4 5 6 7 8 | #!/bin/bash export PATH=$PATH:/xxxx/upsuper.org/usr/bin export CPATH=/xxxx/upsuper.org/usr/include export LIBRARY_PATH=/xxxx/upsuper.org/usr/lib export LD_LIBRARY_PATH=/xxxx/upsuper.org/usr/lib ./$HTTP_CGI |
终于可以了……
这个论原理也很简单,就是把对 cgi 的请求全部转化到一个专门的设置环境变量的 bash 脚本里,然后让它调用相应的 cgi。对了,这个 setenv.sh 必须有执行权限才行……
这一切终于结束了,然后这个生成器也可以用了~
最后是一个小小的东西:
Comments