OpenResty: Your Relational Cloud ---- {{#x|OpenResty}}: Your {{#ci|Relational}} Cloud ☺{{#author|agentzh@yahoo.cn}}☺ {{#author|agentzh (章亦春)}} {{#date|2008.9}} ---- {{#cm|☺}} {{#x|Why}} OpenResty? ---- {{#v|agentzh}}'s {{#x|@boss}} have {{#ci|strong opinions...}} {{img src="#" width="0" height="0"}} {{img src="#" width="0" height="0"}} {{img src="images/boss.png" width="192" height="250"}} ---- {{#v|laser}}: I want a web OS for pure JavaScript sites! {{#v|ting}}: I want a thin web {{#ci|interface}} for our {{#x|Pg cluster}}! {{#v|agentzh}}: ... ---- {{#x|OpenResty}} was {{#ci|born}} since then... ---- {{#x|OpenResty}}'s {{#ci|birthday}}, as seen on the {{#kw|#eeee}} IRC channel... ---- {{#kw|(2007-11-17 01:59:33 PM)}}{{#v|agentzh}}: just created a project for {{#x|OpenAPI}} on openfoundry :) {{#kw|(2007-11-17 02:07:09 PM)}}{{#v|mbot}}: NOTICE: agentzh start to commit to /export/m1/svn/hack {{#kw|(2007-11-17 02:07:10 PM)}}{{#v|mbot}}: NOTICE: {{#ci|created}} the {{#x|openapi}} project {{#kw|(2007-11-17 02:08:44 PM)}}{{#v|mbot}}: NOTICE: [{{#x|openapi}}] initial checkin ---- {{#cm|☺}} Originally {{#ci|Inspired}} by {{#x|Jifty}}'s REST plugin {{img src="images/jifty-rest.png" width="600" height="400"}} ---- ...but have a {{#ci|BIGGER}} ambition. ---- {{#kw|(06:31:53 PM)}}{{#v|ting}}: ft.. 我怎么感觉我们在设计的是 一个{{#x|网络操作系统}}。。...赫赫。。 {{#kw|(06:40:36 PM)}}{{#v|agentzh}}: oh {{#kw|(06:40:42 PM)}}{{#v|agentzh}}: laser's {{#ci|vision}}? {{#kw|(06:40:44 PM)}}{{#v|agentzh}}: *grin* ---- {{#cm|☺}} So {{#x|What}} is {{#ci|OpenResty}} then? ---- {{#v|OpenResty}} is a {{#cm|✓}} {{#ci|general-purpose}} {{#x|RESTful}} web service platform {{#cm|✓}} REST {{#x|wrapper}} for {{#ci|relational}} databases {{#cm|✓}} Virtual web {{#ci|runtime}} for {{#x|RIA}} {{#cm|✓}} \"{{#ci|meta}} web site\" supporting {{#x|other sites}} via web services {{#cm|✓}} handy {{#ci|web database}} which can be accessed from {{#x|anywhere}} ---- {{img src="images/house.png" width="575" height="632"}} ---- {{#cm|☺}} {{#v|agentzh}} loves {{#x|combinations}}, so he designed his API {{#ci|that way}}. ---- {{img src="images/model-comb.png" width="850" height="400"}} ---- {{img src="images/view-comb.png" width="872" height="400"}} ---- {{img src="images/feed-comb.png" width="797" height="400"}} ---- {{img src="images/action-comb.png" width="808" height="400"}} ---- {{img src="images/role-comb.png" width="819" height="400"}} ---- {{#tag|☆}} {{#x|Models}}: Your database {{#ci|tables}} ---- {{#kw|(02:38:03 PM)}}{{#v|laser}}: {{#x|model}} means {{#ci|table}} in RDBMS world, right? {{#kw|(02:38:08 PM)}}{{#v|agentzh}}: yup {{#kw|(02:38:14 PM)}}{{#v|laser}}: and {{#x|column}} means {{#ci|column}} ---- {{img src="images/admin-models.png" width="770" height="632"}} ---- Let's {{#ci|create}} a Post {{#x|model}} for my blog... ---- {{#kw|POST}} {{#x|/=/model/Post}} HTTP/1.0 {{#v|Content-Type}}: {{#x|text/json}} {{#v|Content-Length}}: 111 { {{#kw|"description"}}:\"Blog post\", {{#kw|"columns"}}: [ { {{#kw|"name"}}:\"title\", {{#kw|"label"}}:\"Post title\", {{#kw|"type"}}:\"text\" }, { {{#kw|"name"}}:\"content\", {{#kw|"label"}}:\"Post content\", {{#kw|"type"}}:\"text\" }, { {{#kw|"name"}}:\"author\", {{#kw|"label"}}:\"Post author\", {{#kw|"type"}}:\"text\" }, { {{#kw|"name"}}:\"created\", {{#kw|"default"}}:[\"now()\"], {{#kw|"type"}}:\"timestamp (0) with time zone\", {{#kw|"label"}}:\"Post creation time\" } ], } ---- {{#ci|Insert}} some {{#x|rows}}? ---- {{#kw|POST}} {{#x|/=/model/Post/~/~}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 92 { {{#kw|"title"}}:\"My first post\", {{#kw|"content"}}:\"Blah blah blah...\", {{#kw|"author"}}:\"agentzh\" } ---- Get my {{#ci|rows}} back! ...with {{#x|paging}}... {{#kw|GET}} {{#x|/=/model/Post/~/~?_offset=0&_limit=100}} HTTP/1.0 ---- How about {{#ci|posts}} written by {{#x|agentzh}}? {{#kw|GET}} {{#x|/=/model/Post/author/agentzh}} HTTP/1.0 ---- {{#ci|Posts}} with the title {{#x|containing}} the word {{#v|"OpenResty"}}? {{#kw|GET}} {{#x|/=/model/Post/title/OpenResty?_op=contains}} HTTP/1.0 ---- Okay...I want to {{#ci|add}} a new {{#x|column}} named \"comments\" ---- {{#kw|POST}} {{#x|/=/model/Post/~}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 43 { {{#kw|"name"}}: \"{{#x|comments}}\", {{#kw|"label"}}: \"Number of comments to this post\", {{#kw|"type"}}: \"integer\", {{#kw|"default"}}: 0 } ---- Err...let me see {{#ci|all}} the {{#x|columns}} of the model? {{#kw|GET}} {{#x|/=/model/Post/~}} HTTP/1.0 ---- ...or {{#ci|just}} the definition of the {{#x|author}} column? {{#kw|GET}} {{#x|/=/model/Post/author}} HTTP/1.0 ---- {{#ci|Change}} the {{#x|column name}}? {{#kw|PUT}} {{#x|/=/model/Post/author}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 43 { {{#kw|"name"}}: \"sender\" } ---- Hmm...I'd {{#ci|remove}} this {{#x|column}} from the model! {{#kw|DELETE}} {{#x|/=/model/Post/sender}} HTTP/1.0 ---- {{#tag|☆}} {{#x|Views}}: Your custom {{#ci|SQL}} queries ---- {{#kw|(09:02:20 PM)}}{{#v|laser}}: {{#x|View}} is a very {{#ci|cool}} invention {{#kw|(09:02:30 PM)}}{{#v|laser}}: heh {{#kw|(09:02:52 PM)}}{{#v|laser}}: {{#x|SQL}} is a {{#ci|4th}} generation language ---- {{img src="images/admin-views.png" width="770" height="605"}} ---- {{#kw|POST}} {{#x|/=/view/~}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 312 { {{#kw|"name"}}: \"{{#x|CommentsForAuthor}}\", {{#kw|"description"}}: \"Comments for the specified author\", {{#kw|"definition"}}: \" {{#x|select Comment.sender as guest,}} {{#x|Comment.body as comment_body}} {{#x|from Comment, Post}} {{#x|where Comment.id = Post.id and Post.author = $author}}\" } ---- Get all the {{#ci|comments}} for the {{#x|author}} {{#v|agentzh}} {{#kw|GET}} {{#x|/=/view/CommentsForAuthor/author/agentzh}} HTTP/1.0 Or equivalently {{#kw|GET}} {{#x|/=/view/CommentsForAuthor/~/~?author=agentzh}} HTTP/1.0 ---- The HTTP {{#ci|response}} might be ---- HTTP/1.0 {{#kw|200 OK}} {{#v|Connection}}: close {{#v|Content-Type}}: text/json; charset=UTF-8 {{#v|Content-Length}}: 187 {{#v|Date}}: Mon, 21 Apr 2008 12:42:15 GMT [ { {{#kw|"guest"}}:\"laser\",{{#kw|"comment_body"}}:\"super cool!\" }, { {{#kw|"guest"}}:\"carriezh\",{{#kw|"comment_body"}}:\"hello?hello?\" }, { {{#kw|"guest"}}:\"agentzh\":{{#kw|"comment_body"}}:\"Thanks for commenting!\" } ] ---- {{#tag|☆}} {{#x|Feeds}}: Your {{#ci|RSS/Atom}} for views. ---- {{#kw|(10:38:28 AM)}}{{#v|agentzh}}: at present, {{#x|feed}} objexts are just {{#ci|XML wrappers}} for {{#x|views}}. {{#kw|(10:38:31 AM)}}{{#v|agentzh}}: heh {{#kw|(10:38:37 AM)}}{{#v|agentzh}}: i like this idea. (10:38:28 AM) agentzh: 目前,Feed 对象就是 View 对象的 XML 包裹 (10:38:31 AM) agentzh: 呵呵 (10:38:37 AM) agentzh: 这个概念我挺喜欢的 ---- {{img src="images/admin-feeds.png" width="770" height="605"}} ---- We first define a {{#ci|view}} to serve as the {{#x|data source}} ---- {{#kw|POST}} {{#x|/=/view/PostFeedView}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 312 { {{#kw|"description"}}: \"View for post feed\", {{#kw|"definition"}}: \" {{#x|select author, title,}} {{#x|content, created as published}} {{#x|from Post}} {{#x|order by created desc}} {{#x|limit $count | 20}} \" } ---- Then we create a {{#ci|feed object}} atop this view: ---- {{#kw|POST}} {{#x|/=/feed/Post}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 124 { {{#kw|"description"}}: \"Feed for blog posts\", {{#kw|"author"}}: \"agentzh\", {{#kw|"copyright"}}: \"Copyright 2008 by Yahoo! China EEEE Works\", {{#kw|"language"}}: \"zh-cn\", {{#kw|"title"}}: \"Posts for Human & Machine\", {{#kw|"link"}}: \"http://blog.agentzh.org\", {{#kw|"logo"}}: \"http://blog.agentzh.org/me.jpg\", {{#kw|"view"}}: {{#x|"PostFeedView"}} } ---- The {{#ci|feed}} is ready for {{#x|subscribers}}! {{#kw|GET}} {{#x|/=/feed/Post/~/~}} HTTP/1.0 ---- Or it can take {{#ci|arguments}} just as the underlying {{#x|view}}: {{#kw|GET}} {{#x|/=/feed/Post/count/20}} HTTP/1.0 ---- {{#tag|☆}} {{#x|Actions}}: {{#ci|Compose}} services the way you like ---- {{#kw|(03:17:11 PM)}}{{#v|agentzh}}: {{#x|action}} support is the next milestone {{#kw|(03:17:28 PM)}}{{#v|agentzh}}: so what is an action? {{#kw|(03:17:47 PM)}}{{#v|agentzh}}: an openresty action is a {{#ci|bunch}} of {{#x|restyscript commands}} with a {{#ci|name}} attached to it. ---- {{#ci|Compose}} several {{#x|view invocations}} into one action: {{#kw|POST}} {{#x|/=/action/Init}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 224 { {{#kw|"description"}}: \"Initialization for my site\", {{#kw|"parameters"}}: [], {{#kw|"definition"}}: \" {{#x|GET '/=/view/RecentComments/~/~';}} {{#x|GET '/=/view/RecentPosts/~/~';}} {{#x|GET '/=/model/Post/~/~?_offset=0&_limit=10';}} \" } ---- The server returns the {{#ci|result sets}} for all the {{#x|view calling}}: [ [ {{#ci|...}} ], {{#cm|// the results for the first view}} [ {{#ci|...}} ], {{#cm|// the results for the second view}} [ {{#ci|...}} ] {{#cm|// the results for the third view}} ] ---- {{#cm|♡}} You can also {{#ci|mix}} {{#x|SQL}}, {{#x|HTTP commands}}, and {{#x|JSON}} in one action! ---- {{#kw|POST}} {{#x|/=/action/~}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 189 { {{#kw|"name"}}: \"{{#x|PostComment}}\", {{#kw|"description"}}: \"Action for posting a comment\", {{#kw|"parameters"}}: [ { {{#kw|"name"}}:\"sender\", {{#kw|"type"}}:\"literal\" }, { {{#kw|"name"}}:\"body\", {{#kw|"type"}}:\"literal\" }, { {{#kw|"name"}}:\"post_id\", {{#kw|"type"}}:\"literal\" }, ], ---- {{#kw|"definition"}}: \" {{#x|update Post}} {{#x|set comments = comments + 1}} {{#x|where id = $post_id;}} {{#x|POST '/=/model/Comment/~/~'}} {{#x| { "sender": $sender, "body": $body, "post": $post_id } }} \" } ---- {{#tag|☆}} {{#x|Roles}}: Your custom {{#ci|access control}} (at service level) ---- {{img src="images/admin-roles.png" width="770" height="543"}} ---- Create a {{#ci|read-only}} role {{#kw|POST}} {{#x|/=/role/Reader}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 120 { {{#kw|"description"}}: \"My Read-only role\", {{#kw|"login"}}: \"anonymous\", {{#cm|// or password or captcha}} {{#kw|"password"}}: null } ---- The {{#x|Reader}} role {{#ci|cannot}} access any resources by default, let's {{#ci|add}} some {{#v|ACL rules}} to it. ---- {{#kw|POST}} {{#x|/=/role/Reader/~/~}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 82 [ {{#cm|// allow reading object lists:}} { {{#kw|"method"}}: \"GET\", {{#kw|"url"}}: \"/=/~\" }, {{#cm|// allow reading objects:}} { {{#kw|"method"}}: \"GET\", {{#kw|"url"}}: \"/=/~/~\" }, {{#cm|// allow reading object columns/parameters:}} { {{#kw|"method"}}: \"GET\", {{#kw|"url"}}: \"/=/~/~/~\" }, {{#cm|// allow reading object rows/rules:}} { {{#kw|"method"}}: \"GET\", {{#kw|"url"}}: \"/=/~/~/~/~\" } ] ---- {{#ci|Reader}} roles in action {{#x|(allowed)}}: {{#cm|✓}} {{#kw|GET}} {{#v|/=/model/Post/id/3?_user=agentzh.Reader}} {{#cm|✓}} {{#kw|GET}} {{#v|/=/view?_user=agentzh.Reader}} {{#cm|✓}} {{#kw|GET}} {{#v|/=/role/Reader/method/GET?_user=agentzh.Reader}} {{#cm|✓}} {{#kw|GET}} {{#v|/=/role/Reader?_user=agentzh.Reader}} ---- {{#ci|Reader}} roles in action {{#x|(denied)}}: {{#x|×}} {{#kw|DELETE}} {{#v|/=/model/Post?_user=agentzh.Reader}} {{#x|×}} {{#kw|PUT}} {{#v|/=/model/Post/author?_user=agentzh.Reader}} {{#x|×}} {{#kw|POST}} {{#v|/=/role/Reader/~/~?_user=agentzh.Reader}} ---- {{#tag|☆}} {{#x|Captchas}}: Make your services {{#ci|human-only}} ---- {{#kw|(10:34:50 AM)}}{{#v|Carrie}}: 恩。。。如果我想在{{#ci|发留言}}的时候 {{#kw|(10:34:56 AM)}}{{#v|Carrie}}: 增加个{{#ci|水印}}标志 {{#kw|(10:36:44 AM)}}{{#v|Carrie}}: 我想咱们可能得先实现这个水印的东西。。 。。。因为貌似很需要。。。。 {{#kw|(10:36:56 AM)}}{{#v|agentzh}}: {{#x|验证码图片}}属于一种特殊的{{#ci|角色登录}}方式 {{#kw|(10:37:14 AM)}}{{#v|agentzh}}: 该角色的\"登录密码\"将不放在应用的 JS 中 {{#kw|(10:37:21 AM)}}{{#v|agentzh}}: 而是指向一个验证码 ---- {{img src="images/yisou-captcha.png" width="744" height="479"}} ---- Create an ad-hoc {{#x|role}} with the {{#ci|captcha}} login method {{#kw|POST}} {{#x|/=/role/CommentPoster}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 64 { {{#kw|"description"}}:\"Role for posting comments\", {{#kw|"login"}}:{{#x|"captcha"}} } ---- Assign {{#ci|access}} to the {{#x|operations}} required solving captchas {{#kw|POST}} {{#x|/=/role/CommentPoster/~/~}} HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 48 { {{#kw|"method"}}:\"POST\", {{#kw|"url"}}:\"/=/model/Comment/~/~\" } ---- Then in the {{#ci|client-side}} JavaScript code... {{#kw|GET}} /=/captcha/id {{#cm|# get the captcha id}} {{#kw|GET}} /=/captcha/id/{{#x|[captcha_id]}} {{#cm|# get the captcha image}} {{#kw|POST}} /=/model/Comment/~/~?_user=agentzh.{{#x|CommentPoster}} \\ &_captcha={{#x|[captcha_id]}}:{{#x|[user_solution]}} \\ HTTP/1.0 {{#v|Content-Type}}: text/json {{#v|Content-Length}}: 52 {\"sender\":\"agentzh\",\"body\":\"Good post!\",\"post\":25} ---- {{#v|OpenResty}} offers {{#cm|✓}} (scalable) {{#ci|relational}} data {{#x|storage}} {{#cm|✓}} {{#ci|truely}} {{#x|RESTy}} interface {{#cm|✓}} {{#ci|JSON/YAML}} data transfer format {{#cm|✓}} {{#x|SQL}}-based reusable {{#ci|views}} {{#cm|✓}} a REST-oriented {{#ci|role system}} for {{#x|ACL}} {{#cm|✓}} {{#ci|view}}-based {{#x|RSS feeds}} {{#cm|✓}} user-defined {{#ci|actions}} in {{#x|RestyScript}} {{#cm|✓}} native {{#ci|captchas}} {{#cm|✓}} {{#ci|cross-site}} {{#x|AJAX}} support ---- {{#x|♡}} The OpenResty {{#ci|FastCGI}} server is currently written in {{#x|Perl 5}}. ---- {{img src="images/resty-guts.png" width="914" height="695"}} ---- {{#x|♡}} The {{#x|PgFarm}} backend of OpenResty is designed to be {{#ci|scalable}}. ---- {{img src="images/cluster-arch.png" width="712" height="503"}} ---- {{#cm|☺}} {{#x|OpenResty}} comes with a {{#ci|pony}} (just like {{#x|Jifty}}) {{img src="images/pony.jpg" width="300" height="200"}} ---- {{#kw|(04:49:43 PM)}}{{#v|Carrie}}: 可是你不觉得你现在应该先写出一个{{#x|admin page}}来么 {{#kw|(04:49:55 PM)}}{{#v|Carrie}}: 要不大家以后用你的api就跟{{#ci|背课文}}似的。。。。 ---- {{img src="images/admin-login.png" width="827" height="618"}} ---- {{img src="images/admin-index.png" width="827" height="618"}} ---- {{#x|☺}} It's written in {{#ci|100%}} {{#x|JavaScript}} as well. ---- CPAN modules {{#ci|born}} from {{#x|OpenResty}} {{#cm|✓}} CGI::Cookie::XS {{#cm|✓}} Filter::QuasiQuote ---- OpenResty {{#ci|handlers}} in the {{#x|future}}: {{#x|♡}} /=/mail/... {{#x|♡}} /=/attachment/... {{#x|♡}} /=/template/... {{#x|♡}} /=/git/...? {{#x|♡}} /=/prophet/...? ...{{#ci|Any one?}} ---- OpenResty {{#ci|backends}} in the {{#x|future}}: {{#x|♡}} MySQL {{#x|♡}} Oracle {{#x|♡}} MS SQL Server {{#x|♡}} {{#ci|Put your favorite RDBMS here...}} ---- Or {{#ci|peer-to-peer}} OpenResty {{#x|clusters}} in the future? A {{#ci|cluster}} of {{#ci|clusters}}? ---- {{img src="images/p2p.png" width="528" height="417"}} ---- {{#kw|☺}} {{#ci|Any questions}}? {{#kw|☺}}