什么是Ragel
ragel是一个有限状态机编译器和解释器生成器,它支持生成c/c++、go、java等语言的代码。ragel一般用作文本解析和输入验证。
ragel的核心在于,它是一个确定性有限自动机,核心是由正则表达式运算符和动作嵌入运算符组成。通过动作嵌入运算符,可以改变状态机状态。
Ragel的优势
我们可以使用ragel做文本解析或输入验证。
当你需要解析复杂的HTTP请求,解析从HTTP请求拆分的字符串,再进行某些攻击检测时,传统的正则表达式总是显得有些力不从心。
举个简单的例子
当我们需要匹配a的n次方且b的n次方时,正则表达式无法表达所有状态
1 2 3 4 5
| n=0,1,2... 使用正则表达式怎么写? 如果使用 a*b* 无法保证a的个数和b的个数一致
只有当n是确定且 n==4 时,正则表达式可以写成 a{4}b{4}
|
而ragel可以表示所有状态。下面我将使用ragel生成c代码,表示匹配a的n次方且b的n次方
cout.rl
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
| #include <stdio.h> #include <string.h>
%%{ machine foo; action action_a { a_cout ++; }
action action_b { b_cout ++; } main := 'a'* @action_a 'b'* @action_b ; write data; }%%
int main(int argc, char *argv[]) { int cs; size_t a_cout = 0; size_t b_cout = 0; if(argc > 1){ char *p = argv[1]; char *pe = p + strlen(p); %%{ write init; write exec; }%% }
printf("a_cout=%d\nb_cout=%d", a_cout,b_cout); return 0; }
|
Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| all: compile .PHONY: all
generate: ragel -n -p -o cout.c cout.rl .PHONY: generate
compile: generate gcc cout.c .PHONY: compile
clean: rm -rf a.out .PHONY: clean
|
可以看出,无论我们想输入什么样的形如aaabb字符,我们都可以拥有不同的策略来应对它,而不需要写多个正则表达式。
简单使用Ragel实现解析HTTP请求
下面是我用Ragel实现的一个解析HTTP协议的算法,写得非常简单,但足以看出ragel的解析能力。
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
| #include<stdio.h> #include<string.h> #include<stdlib.h> #include <stdbool.h>
%%{ machine request;
Simple_Method = "GET" | "POST" | "PUT" | "OPTION" ; LF = '\n'; CR = '\r'; CRLF = CR? LF; HTTP_VERSION = ( 'HTTP/' digit+ ( '.' digit+ )? ); url_path = [alnum'/']+ ; header = alnum +': ' alnum+ ; key_value = (alnum '=' alnum) ('&' alnum '=' alnum)? ;
main := Simple_Method ' /' url_path? ('?'key_value*)? ' ' HTTP_VERSION CRLF (header CRLF)+ @{ success = true;} ; }%%
%% write data;
int main(int argc,char * argv[]) { if(argc > 1) { int cs; char *request; FILE *file = fopen(argv[1],"rb"); if(file == NULL){ return 0; }
fseek(file, 0, SEEK_END); int length = ftell(file); fseek(file, 0, SEEK_SET);
request = (char *)malloc(length + 1); fread(request, 1, length, file); fclose(file);
bool success = false; char *p = request; char *pe = request + length; char *eof = pe;
%% write init; %% write exec;
if (success) { printf("HTTP 格式正确"); } }
}
|
Ragel 强大的状态转移以及运算符
1 2 3 4 5 6
| '>' 在匹配第一个字符成功后立即执行action,此时p指向匹配的第一个字符 '@' 尽可能长的完全匹配所有字符后执行action,此时p指向匹配的最后一个字符 '%' 完整匹配所有字符后执行action,此时p指向匹配的最后一个字符
注:关于'@'和'%'的使用可以自己做实验简单测试一下
|
总结
Ragel作为一个确定有限状态机,能在状态转移过程中插入动作,可随时更改转移状态,在文本解析或者协议解析方面有着非常大的优势。且可以生成c/c++、go、java代码任意调用,非常具有便携性。