midori.rb 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +9 -0
  3. data/LICENSE +21 -0
  4. data/ext/midori/extconf.rb +4 -0
  5. data/ext/midori/websocket.c +32 -0
  6. data/lib/midori/api.rb +426 -0
  7. data/lib/midori/api_engine.rb +109 -0
  8. data/lib/midori/clean_room.rb +24 -0
  9. data/lib/midori/configure.rb +12 -0
  10. data/lib/midori/connection.rb +59 -0
  11. data/lib/midori/const.rb +118 -0
  12. data/lib/midori/core_ext/configurable.rb +33 -0
  13. data/lib/midori/core_ext/define_class.rb +29 -0
  14. data/lib/midori/core_ext/proc.rb +13 -0
  15. data/lib/midori/core_ext/string.rb +29 -0
  16. data/lib/midori/env.rb +18 -0
  17. data/lib/midori/eventsource.rb +20 -0
  18. data/lib/midori/exception.rb +22 -0
  19. data/lib/midori/logger.rb +15 -0
  20. data/lib/midori/middleware.rb +31 -0
  21. data/lib/midori/request.rb +115 -0
  22. data/lib/midori/response.rb +34 -0
  23. data/lib/midori/route.rb +20 -0
  24. data/lib/midori/runner.rb +63 -0
  25. data/lib/midori/sandbox.rb +46 -0
  26. data/lib/midori/server.rb +106 -0
  27. data/lib/midori/version.rb +5 -0
  28. data/lib/midori/websocket.rb +105 -0
  29. data/lib/midori.rb +37 -0
  30. data/midori.sublime-project +16 -0
  31. data/tutorial/README.md +11 -0
  32. data/tutorial/SUMMARY.md +28 -0
  33. data/tutorial/advanced/custom_extensions.md +0 -0
  34. data/tutorial/advanced/deploying_for_production.md +0 -0
  35. data/tutorial/advanced/error_handling.md +0 -0
  36. data/tutorial/advanced/rerl.md +0 -0
  37. data/tutorial/advanced/scaling_project.md +0 -0
  38. data/tutorial/advanced/unit_testing.md +0 -0
  39. data/tutorial/essentials/extensions.md +31 -0
  40. data/tutorial/essentials/getting_started.md +27 -0
  41. data/tutorial/essentials/installation.md +93 -0
  42. data/tutorial/essentials/middlewares.md +88 -0
  43. data/tutorial/essentials/request_handling.md +74 -0
  44. data/tutorial/essentials/routing.md +136 -0
  45. data/tutorial/essentials/runner.md +59 -0
  46. data/tutorial/meta/comparison_with_other_frameworks.md +0 -0
  47. data/tutorial/meta/join_the_midori_community.md +0 -0
  48. data/tutorial/meta/next_steps.md +0 -0
  49. metadata +155 -0
@@ -0,0 +1,106 @@
1
+ ##
2
+ # Logic to EventMachine TCP Server, running inside +Midori::Connection+
3
+ module Midori::Server
4
+ # @!attribute request
5
+ # @return [Midori::Request] raw request
6
+ # @!attribute api
7
+ # @return [Class] inherited from Midori::API
8
+ # @!attribute websocket
9
+ # @return [Midori::WebSocket] defined websocket instance
10
+ # @!attribute eventsource
11
+ # @return [Midori::EventSource] defined eventsource instance
12
+ attr_accessor :request, :api, :websocket, :eventsource
13
+
14
+ # Define server behaviour
15
+ # @param [Class] api inherited from Midori::API
16
+ # @param [Logger] logger global logger
17
+ def server_initialize(api, logger)
18
+ @api = api
19
+ @logger = logger
20
+ @request = Midori::Request.new
21
+ @websocket = Midori::WebSocket.new(self)
22
+ @eventsource = Midori::EventSource.new(self)
23
+ end
24
+
25
+ # Logic of receiving data
26
+ # @param [String] monitor the socket able to read
27
+ def receive_data(monitor)
28
+ lambda do
29
+ async_fiber(Fiber.new do
30
+ begin
31
+ _sock_domain, remote_port, _remote_hostname, remote_ip = monitor.io.peeraddr
32
+ port, ip = remote_port, remote_ip
33
+ @request.ip = ip
34
+ @request.port = port
35
+ data = monitor.io.read_nonblock(16_384)
36
+ if @request.parsed? && @request.body_parsed?
37
+ websocket_request(StringIO.new(data))
38
+ else
39
+ @request.parse(data)
40
+ receive_new_request if @request.parsed && @request.body_parsed?
41
+ end
42
+ rescue => e
43
+ close_connection
44
+ @logger.warn "#{@request.ip} - - #{e.class} #{e.backtrace.join("\n")}".yellow
45
+ end
46
+ end)
47
+ end.call
48
+ end
49
+
50
+ # Logic of receiving new request
51
+ def receive_new_request
52
+ begin
53
+ start_time = Time.now
54
+ @response = @api.receive(request, self)
55
+ now_time = Time.now
56
+ @logger.info "#{@request.ip} - - \"#{@request.method} #{@request.path} HTTP/#{@request.protocol.join('.')}\" #{@response.status} #{sprintf("%.6f", now_time.to_f - start_time.to_f)}".green
57
+ call_event(:open) if @request.websocket?
58
+ rescue Midori::Exception::NotFound => e
59
+ @response = Midori::Sandbox.capture(e)
60
+ rescue => e
61
+ @response = Midori::Sandbox.capture(e)
62
+ @logger.error e.inspect.red
63
+ @logger.warn e.backtrace.join("\n").yellow
64
+ end
65
+ unless @request.websocket? || @request.eventsource?
66
+ send_data @response
67
+ close_connection_after_writing
68
+ end
69
+ end
70
+
71
+ # Logic of receiving WebSocket request
72
+ # @param [StringIO] data raw data
73
+ def websocket_request(data)
74
+ @websocket.decode(data)
75
+ case @websocket.opcode
76
+ when 0x1, 0x2
77
+ call_event(:message, [@websocket.msg])
78
+ when 0x9
79
+ @websocket.pong(@websocket.msg)
80
+ call_event(:ping)
81
+ when 0xA
82
+ call_event(:pong)
83
+ end
84
+ rescue Midori::Exception::FrameEnd => _e
85
+ call_event(:close)
86
+ send_data "\b" # Opcode 0x8
87
+ close_connection_after_writing
88
+ rescue Midori::Exception::PingPongSizeTooLarge => e
89
+ @logger.warn e.inspect.yellow
90
+ call_event(:error) # Too large ping request
91
+ send_data "\b" # Opcode 0x8
92
+ close_connection_after_writing
93
+ rescue => e
94
+ call_event(:error)
95
+ @logger.error e.inspect.red
96
+ @logger.warn e.backtrace.join("\n").yellow
97
+ close_connection_after_writing
98
+ end
99
+
100
+ # To call a websocket event if it exist
101
+ # @param [Symbol] event event name
102
+ # @param [Array] args arg list
103
+ def call_event(event, args = [])
104
+ -> { @websocket.instance_exec(*args, &@websocket.events[event]) }.call unless @websocket.events[event].nil?
105
+ end
106
+ end
@@ -0,0 +1,5 @@
1
+ # Midori Module
2
+ module Midori
3
+ # Current Version Code
4
+ VERSION = '0.4.3'.freeze
5
+ end
@@ -0,0 +1,105 @@
1
+ ##
2
+ # This class provides methods for WebSocket connection instance.
3
+ # @attr [Array<Integer>, String] msg message send from client
4
+ # @attr [Integer] opcode operation code of WebSocket
5
+ # @attr [Hash] events response for different event
6
+ # @attr [Midori::Connection] connection raw EventMachine connection
7
+ # @attr [Midori::Request] request raw request
8
+ class Midori::WebSocket
9
+ attr_accessor :msg, :opcode, :events, :connection, :request
10
+
11
+ # Init a WebSocket instance with a connection
12
+ # @param [Midori::Connection] connection raw EventMachine connection
13
+ def initialize(connection)
14
+ @events = {}
15
+ @connection = connection
16
+ end
17
+
18
+ # Decode raw data send from client
19
+ # @param [StringIO] data raw data
20
+ def decode(data)
21
+ # Fin and Opcode
22
+ byte_tmp = data.getbyte
23
+ fin = byte_tmp & 0b10000000
24
+ @opcode = byte_tmp & 0b00001111
25
+ # NOT Support Multiple Fragments
26
+ raise Midori::Exception::ContinuousFrame unless fin
27
+ raise Midori::Exception::OpCodeError unless [0x1, 0x2, 0x8, 0x9, 0xA].include? opcode
28
+ close if @opcode == 0x8 # Close Frame
29
+ # return if @opcode == 0x9 || @opcode == 0xA # Ping Pong
30
+ decode_mask(data)
31
+ end
32
+
33
+ # Decode masked message send from client
34
+ # @param [StringIO] data raw data
35
+ def decode_mask(data)
36
+ # Mask
37
+ byte_tmp = data.getbyte
38
+ is_masked = byte_tmp & 0b10000000
39
+ raise Midori::Exception::NotMasked unless is_masked == 128
40
+ # Payload
41
+ payload = byte_tmp & 0b01111111
42
+ mask = Array.new(4) { data.getbyte }
43
+ # Message
44
+ masked_msg = Array.new(payload) { data.getbyte }
45
+ @msg = self.mask(masked_msg, mask)
46
+ @msg = @msg.pack('C*').force_encoding('utf-8') if [0x1, 0x9, 0xA].include?opcode
47
+ # For debug
48
+ # data.rewind
49
+ # data.bytes {|byte| puts byte.to_s(16)}
50
+ end
51
+
52
+ # API definition for events
53
+ # @param [Symbol] event event name(open, message, close, ping, pong)
54
+ # @yield what to do after event matched
55
+ # @example
56
+ # websocket '/websocket' do |ws|
57
+ # ws.on :message do |msg|
58
+ # puts msg
59
+ # end
60
+ # end
61
+ def on(event, &block) # open, message, close, ping, pong
62
+ @events[event] = block
63
+ end
64
+
65
+ # Send data
66
+ # @param [Array<Integer>, String] msg data to send
67
+ def send(msg)
68
+ output = []
69
+ if msg.is_a?String
70
+ output << 0b10000001 << msg.size << msg
71
+ @connection.send_data(output.pack("CCA#{msg.size}"))
72
+ elsif msg.is_a?Array
73
+ output << 0b10000010 << msg.size
74
+ output.concat msg
75
+ @connection.send_data(output.pack('C*'))
76
+ else
77
+ raise Midori::Exception::OpCodeError
78
+ end
79
+ end
80
+
81
+ # Send a Ping request
82
+ # @param [String] str string to send
83
+ def ping(str)
84
+ heartbeat(0b10001001, str)
85
+ end
86
+
87
+ # Send a Pong request
88
+ # @param [String] str string to send
89
+ def pong(str)
90
+ heartbeat(0b10001010, str)
91
+ end
92
+
93
+ # Ancestor of ping pong
94
+ # @param [Integer] method opcode
95
+ # @param [String] str string to send
96
+ def heartbeat(method, str)
97
+ raise Midori::Exception::PingPongSizeTooLarge if str.size > 125
98
+ @connection.send_data [method, str.size, str].pack("CCA#{str.size}")
99
+ end
100
+
101
+ # Close a websocket connection
102
+ def close
103
+ raise Midori::Exception::FrameEnd
104
+ end
105
+ end
data/lib/midori.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'cgi'
2
+ require 'digest/sha1'
3
+ require 'stringio'
4
+ require 'fiber'
5
+ require 'logger'
6
+ require 'http/parser'
7
+ require 'mustermann'
8
+ require 'murasaki'
9
+ require 'socket'
10
+
11
+ require_relative 'midori_ext'
12
+ require_relative 'midori/core_ext/configurable'
13
+ require_relative 'midori/core_ext/string'
14
+ require_relative 'midori/core_ext/define_class'
15
+ require_relative 'midori/core_ext/proc'
16
+
17
+ require_relative 'midori/version'
18
+
19
+ require_relative 'midori/const'
20
+ require_relative 'midori/exception'
21
+ require_relative 'midori/env'
22
+ require_relative 'midori/clean_room'
23
+ require_relative 'midori/server'
24
+ require_relative 'midori/connection'
25
+ require_relative 'midori/request'
26
+ require_relative 'midori/response'
27
+ require_relative 'midori/api'
28
+ require_relative 'midori/api_engine'
29
+ require_relative 'midori/route'
30
+ require_relative 'midori/sandbox'
31
+ require_relative 'midori/websocket'
32
+ require_relative 'midori/eventsource'
33
+ require_relative 'midori/middleware'
34
+ require_relative 'midori/configure'
35
+ require_relative 'midori/runner'
36
+ require_relative 'midori/logger'
37
+
@@ -0,0 +1,16 @@
1
+ {
2
+ "folders":
3
+ [
4
+ {
5
+ "path": ".",
6
+ "folder_exclude_patterns": [".resources", "coverage"],
7
+ "file_exclude_patterns": ["midori-*.gem","*.sublime-workspace","logo.png",".DS_Store","CODE_OF_CONDUCT.md","CONTRIBUTING.md"]
8
+ }
9
+ ],
10
+ "settings":
11
+ {
12
+ "tab_size": 2,
13
+ "translate_tabs_to_spaces": true,
14
+ "trim_trailing_white_space_on_save": true
15
+ }
16
+ }
@@ -0,0 +1,11 @@
1
+ # Introduction
2
+
3
+ ## What is midori?
4
+
5
+ Midori (pronounced /miːdɒliː/) is a **Ruby web framework** for building APIs, web pages and realtime web services. Midori is designed to provide **non-blocking** I/O operations without **callback hells** on web development. The core library is focused on network I/Os, and gives a very easy DSL to pick up and converting existed projects on. On the other hand, midori also officially provides extension libraries that could deal with file, database, cache, network requests and other I/O operations without blocking.
6
+
7
+ If you want to know how midori compares to other libraries/frameworks, checkout out the [Comparison with Other Frameworks](meta/comparison_with_other_frameworks.md)
8
+
9
+ ## Note
10
+
11
+ The official guide assumes intermediate level knowledge of Ruby and backend development. If you are totally new , it might not be the best idea to jump right into a framework as your first step - grasp the basics then come back! Prior experience with other frameworks like Rails helps, but is not required.
@@ -0,0 +1,28 @@
1
+ # Summary
2
+
3
+ ## Essentials
4
+
5
+ * [Introduction](README.md)
6
+ * [Installation](essentials/installation.md)
7
+ * [Getting Started](essentials/getting_started.md)
8
+ * [Routing](essentials/routing.md)
9
+ * [Request Handling](essentials/request_handling.md)
10
+ * [Runner](essentials/runner.md)
11
+ * [Middlewares](essentials/middlewares.md)
12
+ * [Extensions](essentials/extensions.md)
13
+
14
+
15
+ ## Advanced
16
+
17
+ - [Error Handling](advanced/error_handling.md)
18
+ - [Request-Eval-Response Loop](advanced/rerl.md)
19
+ - [Custom Extensions](advanced/custom_extensions.md)
20
+ - [Scaling Project](advanced/scaling_project.md)
21
+ - [Unit Testing](advanced/unit_testing.md)
22
+ - [Deploying for Production](advanced/deploying_for_production.md)
23
+
24
+ ## Meta
25
+
26
+ - [Comparison with Other Frameworks](meta/comparison_with_other_frameworks.md)
27
+ - [Next Steps](meta/next_steps.md)
28
+ - [Join the Midori Community](meta/join_the_midori_community.md)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,31 @@
1
+ # Extensions
2
+
3
+ Extensions are rubygems that could combine other gems with midori through meta-programming.
4
+ There's a set of officially supported midori extensions called `midori-contrib`.
5
+ This contains gems commonly used like Database ORM, Redis Driver, Redis ORM, HTTP Driver, etc.
6
+
7
+ To include it inside your project, be sure to get the original gem installed without require it.
8
+ Then include specific parts of `midori-contrib` to make it work.
9
+
10
+ For example:
11
+
12
+ `Gemfile`
13
+
14
+ ```ruby
15
+ source 'https://rubygems.org'
16
+
17
+ gem 'bundler', '~> 1.0'
18
+ gem 'rake', '~> 12.0'
19
+
20
+ gem 'hiredis', '~> 0.6.0', require: false
21
+ gem 'ohm', '~> 3.0'
22
+ gem 'sequel', '~> 5.0', require: false
23
+ gem 'mysql2', '~> 0.4', require: false
24
+
25
+ gem 'em-midori', '~> 0.4.3', require: 'midori'
26
+ gem 'midori-contrib', '~> 0.1.0', require: %w(
27
+ midori-contrib
28
+ midori-contrib/sequel/mysql2
29
+ midori-contrib/redic
30
+ )
31
+ ```
@@ -0,0 +1,27 @@
1
+ # Getting Started
2
+
3
+ ## Hello Midori
4
+
5
+ midori is a [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for web and API development in Ruby with minimal effort:
6
+
7
+ ```ruby
8
+ # hello_midori.rb
9
+ require 'midori'
10
+
11
+ class HelloWorldAPI < Midori::API
12
+ get '/' do
13
+ 'Ohayou Midori'
14
+ end
15
+ end
16
+
17
+ Midori::Runner.new(HelloWorldAPI).start
18
+ ```
19
+
20
+ Run with
21
+
22
+ ```
23
+ $ ruby hello_midori.rb
24
+ ```
25
+
26
+ View at: http://127.0.0.1:8080 with your browser, if you see a page showing: **Ohayou Midori**. You've made your first application with midori.
27
+
@@ -0,0 +1,93 @@
1
+ # Installation
2
+
3
+ ## Requirements
4
+
5
+ Open up a command line prompt. Any commands prefaced with a dollar sign `$` should be run in the command line. Verify that you have a current version of Ruby installed:
6
+
7
+ ```
8
+ $ ruby -v
9
+ ruby 2.4.1p111
10
+ ```
11
+
12
+ Generally, midori supports the following ruby interpreters:
13
+
14
+ - Ruby (MRI) **>= 2.2.6**
15
+
16
+ For every version released, it would be tested and **officially ensured** in the following environments:
17
+
18
+ - Ruby (MRI)
19
+ - 2.2.8
20
+ - 2.3.5
21
+ - 2.4.2
22
+
23
+ **Note: **
24
+
25
+ - **For JRuby users, due to some C extensions used in midori, it is still unable to run on the current version of JRuby.**
26
+ - **For macOS users, you may meet performance problem due to the issue of [nio4r](https://github.com/socketry/nio4r/issues/125). Very few people would use macOS in production, so this issue may not affect much. We would still be working hard on fixing it, but the issue wouldn't be a high priority one.**
27
+
28
+ It's hard to say that if it is possible for running on other ruby implementations like Rubinius or RubyMotion, if you're in favor of supporting more ruby implementations, you could open a ticket [here](https://github.com/heckpsi-lab/em-midori/issues), and we are glad to discuss it.
29
+
30
+ ## Install with RubyGems
31
+
32
+ ```
33
+ $ gem install em-midori
34
+ Successfully installed em-midori-0.4.3
35
+ 1 gem installed
36
+ ```
37
+
38
+ To test whether it has installed properly, run:
39
+
40
+ ```
41
+ $ ruby -r "midori" -e "class A < Midori::API;end;Midori::Runner.new(A).start"
42
+ ```
43
+
44
+ If you see the following message, then everything now works fine.
45
+
46
+ ```
47
+ Midori 0.4.3 is now running on 127.0.0.1:8080
48
+ ```
49
+
50
+ ## Use Bundler
51
+
52
+ Example `Gemfile` of basic usage as following:
53
+
54
+ ```ruby
55
+ source 'https://rubygems.org'
56
+ gem 'bundler', '~> 1.0'
57
+ gem 'em-midori', '~> 0.4', require: 'midori'
58
+ ```
59
+
60
+ and then running:
61
+
62
+ ```
63
+ $ bundle install
64
+ ```
65
+
66
+ You could use
67
+
68
+ ```ruby
69
+ require 'bundler'
70
+ Bundler.require
71
+ ```
72
+
73
+ in your entrance ruby file.
74
+
75
+ To include built-in extensions of midori you could make your `Gemfile` like:
76
+
77
+ ```ruby
78
+ source 'https://rubygems.org'
79
+ gem 'bundler', '~> 1.0'
80
+ gem 'em-midori', '~> 0.4', require: 'midori'
81
+ gem 'midori-contrib', '~> 0.0.1', require: %w'midori-contrib/file midori-contrib/sequel/mysql2'
82
+ ```
83
+
84
+ Using bunlder could make dependency management much easier, which helps a lot in scaling project. To learn more about bundler, you could see docs [here](http://bundler.io/docs.html).
85
+
86
+ ## For Developers in China
87
+
88
+ You may probably meet problems with rubygems due to unstable overseas internet connection issues in China. The most popular way to solve it is to use mirror provided by [RubyChina](https://gems.ruby-china.org/) or [TUNA](https://mirror.tuna.tsinghua.edu.cn/help/rubygems/) as your gem source. This may have some side effects in development, because there's a few minutes' delay in receiving gem updates.
89
+
90
+ Alternatively, you could use proxy to connect to the main repository directly to avoid the delay problem. But using proxy is a little too complex in production environment.
91
+
92
+ Choose the solution better fits your requirements. Mixing the solutions like using proxy in development and using mirror in production is also a good choice.
93
+
@@ -0,0 +1,88 @@
1
+ # Middlewares
2
+
3
+ Middlewares are very exciting features midori support.
4
+ As it has been implemented for lots of web frameworks.
5
+ Middlewares from midori behaves very different from other frameworks, which greatly helps reducing complexity and improving performance in scaling projects.
6
+
7
+ ## Basic Usage
8
+
9
+ To begin with, inheritate the `Midori::Middleware` class. Here's an example:
10
+
11
+ ```ruby
12
+ class JSONMiddleware < Midori::Middleware
13
+ def before(request)
14
+ request.body = JSON.parse(request.body) unless request.body == ''
15
+ request
16
+ end
17
+
18
+ def after(_request, response)
19
+ response.header['Content-Type'] = 'application/json'
20
+ response.body = response.body.to_json
21
+ response
22
+ end
23
+ end
24
+ ```
25
+
26
+ To use middleware inside router, there're two possible ways.
27
+ One is through `use` method, which would affect all routes in the current scope.
28
+ The other is through `filter` method, which would affect the following route definition only.
29
+
30
+ Here are some examples.
31
+
32
+ ```ruby
33
+ class API < Midori::API
34
+ use AMiddleware
35
+
36
+ filter BMiddleware
37
+ get '/' do
38
+ # Both AMiddleware and BMiddleware works here.
39
+ 'Hello'
40
+ end
41
+
42
+ get '/a' do
43
+ # Only AMiddleware is involved here.
44
+ 'World'
45
+ end
46
+ end
47
+ ```
48
+
49
+ ## Stack-less Design
50
+
51
+ For rack users, middlewares are always considered as parts of stack.
52
+ Deep stacks may cause several performance issues in Ruby, including increasing cost when context switching, thread switching and process forking, etc.
53
+ For frameworks like Rails, it defaulty contains lots of middlewares which enlarge this problem.
54
+
55
+ In midori, middlewares are no longer stack-based.
56
+ It uses a loop-based system to run middleware code.
57
+ This makes using of middlewares with less side effects.
58
+
59
+ ## Early Exit Feature
60
+
61
+ Early exit feature is also supported in midori without stack.
62
+ To make it happen, just respond an `Midori::Response` object during request processing.
63
+ Here's an example for CORS middleware using early exit feature.
64
+
65
+ ```ruby
66
+ class CorsMiddleware < Midori::Middleware
67
+ def before(request)
68
+ if request.method == :OPTIONS
69
+ # PREFLIGHT
70
+ return Midori::Response.new(
71
+ header: {
72
+ 'Access-Control-Allow-Origin': request.header['Origin'],
73
+ 'Access-Control-Request-Headers': 'Token',
74
+ 'Access-Control-Allow-Headers': 'Token',
75
+ 'Access-Control-Allow-Methods': 'OPTIONS, POST, PUT, DELETE'
76
+ }
77
+ )
78
+ else
79
+ request
80
+ end
81
+ end
82
+
83
+ def after(request, response)
84
+ response.header['Access-Control-Allow-Origin'] = request.header['Origin']
85
+ response
86
+ end
87
+ end
88
+ ```
@@ -0,0 +1,74 @@
1
+ # Request Handling
2
+
3
+ ## Accessing the request object
4
+
5
+ The incoming request object can be accessed from request level (filter, routes, error handlers) through the `request` method:
6
+
7
+ ```ruby
8
+ class ExampleAPI < Midori::API
9
+ get '/' do
10
+ request.ip # '127.0.0.1' client ip address
11
+ request.port # '8080' client port
12
+ request.method # 'GET'
13
+ request.path # '/'
14
+ request.query_string # ''
15
+ request.query_params # {} query params, parsed from query string
16
+ request.header # {} request header
17
+ request.body # request body sent by the client
18
+ request.params # {} params matched in router
19
+ request.cookie # {} cookie parsed from header
20
+ end
21
+ end
22
+ ```
23
+
24
+ **Note (for sinatra users): **The `request.body` in modori is a `String` object but not a `StringIO` object.
25
+
26
+ ## Construct the response object
27
+
28
+ Midori accepts the return value of the block as the response body by default.
29
+
30
+ You could edit variable `status` and `header` to construct things other than body.
31
+
32
+ You could also return a `Midori::Response` object as your response, which could override everything.
33
+
34
+ ```ruby
35
+ class ExampleAPI < Midori::API
36
+ get '/case_0' do
37
+ 'Hello'
38
+ # HTTP/1.1 200 OK
39
+ # Server: Midori/1.0
40
+ #
41
+ # Hello
42
+ end
43
+
44
+ get '/case_1' do
45
+ @status = 418
46
+ "I\'m a teapot"
47
+ # HTTP/1.1 418 I'm a teapot
48
+ # Server: Midori/1.0
49
+ #
50
+ # I'm a teapot
51
+ end
52
+
53
+ get '/case_2' do
54
+ @header['Example-Header'] = 'Example-Value'
55
+ 'Hello'
56
+ # HTTP/1.1 200 OK
57
+ # Server: Midori/1.0
58
+ # Example-Header: Example-Value
59
+ #
60
+ # Hello
61
+ end
62
+
63
+ get '/case_3' do
64
+ @status = 202
65
+ @header['Example-Header'] = 'Example-Value'
66
+ Midori::Response.new(status: 200, header: {}, body: 'Hello') # Overrides everything else
67
+ # HTTP/1.1 200 OK
68
+ # Server: Midori/1.0
69
+ #
70
+ # Hello
71
+ end
72
+ end
73
+ ```
74
+