midori.rb 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +9 -0
- data/LICENSE +21 -0
- data/ext/midori/extconf.rb +4 -0
- data/ext/midori/websocket.c +32 -0
- data/lib/midori/api.rb +426 -0
- data/lib/midori/api_engine.rb +109 -0
- data/lib/midori/clean_room.rb +24 -0
- data/lib/midori/configure.rb +12 -0
- data/lib/midori/connection.rb +59 -0
- data/lib/midori/const.rb +118 -0
- data/lib/midori/core_ext/configurable.rb +33 -0
- data/lib/midori/core_ext/define_class.rb +29 -0
- data/lib/midori/core_ext/proc.rb +13 -0
- data/lib/midori/core_ext/string.rb +29 -0
- data/lib/midori/env.rb +18 -0
- data/lib/midori/eventsource.rb +20 -0
- data/lib/midori/exception.rb +22 -0
- data/lib/midori/logger.rb +15 -0
- data/lib/midori/middleware.rb +31 -0
- data/lib/midori/request.rb +115 -0
- data/lib/midori/response.rb +34 -0
- data/lib/midori/route.rb +20 -0
- data/lib/midori/runner.rb +63 -0
- data/lib/midori/sandbox.rb +46 -0
- data/lib/midori/server.rb +106 -0
- data/lib/midori/version.rb +5 -0
- data/lib/midori/websocket.rb +105 -0
- data/lib/midori.rb +37 -0
- data/midori.sublime-project +16 -0
- data/tutorial/README.md +11 -0
- data/tutorial/SUMMARY.md +28 -0
- data/tutorial/advanced/custom_extensions.md +0 -0
- data/tutorial/advanced/deploying_for_production.md +0 -0
- data/tutorial/advanced/error_handling.md +0 -0
- data/tutorial/advanced/rerl.md +0 -0
- data/tutorial/advanced/scaling_project.md +0 -0
- data/tutorial/advanced/unit_testing.md +0 -0
- data/tutorial/essentials/extensions.md +31 -0
- data/tutorial/essentials/getting_started.md +27 -0
- data/tutorial/essentials/installation.md +93 -0
- data/tutorial/essentials/middlewares.md +88 -0
- data/tutorial/essentials/request_handling.md +74 -0
- data/tutorial/essentials/routing.md +136 -0
- data/tutorial/essentials/runner.md +59 -0
- data/tutorial/meta/comparison_with_other_frameworks.md +0 -0
- data/tutorial/meta/join_the_midori_community.md +0 -0
- data/tutorial/meta/next_steps.md +0 -0
- 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,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
|
+
}
|
data/tutorial/README.md
ADDED
@@ -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.
|
data/tutorial/SUMMARY.md
ADDED
@@ -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
|
+
|