-
Notifications
You must be signed in to change notification settings - Fork 0
/
rebased.cr
executable file
路243 lines (204 loc) 路 5.7 KB
/
rebased.cr
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#!/usr/bin/env crystal
# Required for high-performance routing
require "router"
# Required for JSON datastorage and RESTFul API
require "json"
# Rebase API server, compatible with API __v0.7.2__
# Copyright(C) 2018 duangsuse
# License: AGPL-3.0
# Author: duangsuse
# RebaseD server version
VERSION = "0.1.0"
# Compatible Rebase API Version
API_VERSION = "0.7.2"
# A simple Crystal `HTTP::Server` CORS header middleware
#
# It adds `Access-Control-Allow-Origin` header as `*` in response context
#
# To use as **CORS** middleware
#
# ```
# HTTP::Server.new([route_handler, CORSHandler.new])
# ```
class CORSHandler
include HTTP::Handler
# CORS header handler
# Overrides `HTTP::Handler#call`, and call next handler
def call(context : HTTP::Server::Context) : Void
# Adds CORS header - allow all
context.response.headers["Access-Control-Allow-Origin"] = "*"
call_next(context)
end
end
# Another simple Crystal `HTTP::Server` CORS header middleware
#
# It sets `Content-Type` header as `Content-Type: application/json; charset=utf-8` in response context
#
# To use as **JSON API** middleware
#
# ```
# HTTP::Server.new([route_handler, JSONHandler.new])
# ```
class JSONHandler
include HTTP::Handler
# Content-Type field
CONTENT_TYPE_HEADER = "Content-Type"
# JSON REST content type value
JSON_TYPE = "application/json; charset=utf-8"
# JSON header handler
# Overrides `HTTP::Handler#call`, and call next handler
def call(context : HTTP::Server::Context) : Void
# Make JSON header - charset=utf-8
context.response.headers[CONTENT_TYPE_HEADER] = JSON_TYPE
call_next(context)
end
end
# Rebase data models storage
#
# Execute queries, make data persistence, generate JSONs
#
# Including these models
#
# + User model
# + Category model
# + Feed model
class RebaseModel
struct User
end
struct Category
end
struct Feed
end
def initialize
end
end
# Alias for `HTTP::Server::Context`
alias Context = HTTP::Server::Context
# A simple [Rebase API](https://github.com/drakeet/rebase-api) server
#
# RESTFul HTTP Web server, using a JSON data storage `RebaseModel`
#
# See API document [here](https://github.com/duangsuse/RebaseD/blob/master/rebase-api.md)
#
# Using __router.cr__ and __Radix tree__ to route requests
class RebaseServer
include Router # Router mixin
# Host environment variable name
HOST_ENV = "REBASED_HOST"
# Port environment variable name
PORT_ENV = "REBASED_PORT"
# Default storage file
DEFAULT_DATABASE = "rebase.json"
# The datas torage property
property datastorage : RebaseModel
# Initialize with storage
def initialize(@datastorage : RebaseModel = RebaseModel.new)
end
# Print a line of text to context response, overriding `HTTP::Server::Response#content_type`
#
# *ctx* references to target context
#
# *text* is the text to print
def text(ctx : Context, text : String) : String
ctx.response.content_type = "text/plain"
ctx.response.print(text)
return text
end
# Print text to context response
#
# *ctx* references to target context
#
# *content* is the text to print
def rputs(ctx : Context, content : String) : String
ctx.response.print(content)
return content
end
# Set response code of an `HTTP::Server::Context` object
#
# + *ctx* context Object
# + *status* status code, default __404__
def response_code(ctx : Context, status = 404)
ctx.response.status_code = status
end
# Content-Type changer for Context object
#
# + *ctx* `Context` object to use
# + *label* `Content-Type` header value
#
# *Return* new `Content-Type` header value
def content_type(ctx : Context, label : String = "text/plain") : String
ctx.response.content_type = label
return label
end
# Get POST query string from context
#
# + *ctx* `Context` object to use
# + *name* param name
#
# *Return* param value or `nil`
def query(ctx : Context, name : String) : (String | Nil)
request.query_params.has_key?(name) ? request.query_params[name].encode("utf-8") : nil
end
# Setup radix routers in application
#
# See API Documents [here](https://github.com/duangsuse/RebaseD#api--%E7%BD%91%E7%BB%9C%E6%8E%A5%E5%8F%A3)
#
# + __GET__ /
# + __GET__ /version
# + __GET__ /api-version
def routerize! : Void
# API Index page
get "/" do |ctx|
text(ctx, "Hello World!")
next ctx
end
# Program version
get "/server-version" do |ctx|
text(ctx, VERSION)
next ctx
end
# API version
get "/version" do |ctx|
text(ctx, API_VERSION)
next ctx
end
end
# Run application
#
# Make use of environment `PORT_ENV`
#
# *host* listening host, default __127.0.0.1__
#
# Will be **overrided** by environment variable `REBASED_HOST`
#
# *port* listening port, default __8080__
#
# Will be **overrided** by environment variable `REBASED_PORT`
def run(host = "127.0.0.1", port : Int32 = 8080) : Nil
# Make server configured
port = ENV[PORT_ENV].to_i if ENV.has_key?(PORT_ENV) # Port
host = ENV[HOST_ENV] if ENV.has_key?(HOST_ENV) # Host
# Initialize Crystal server
server = HTTP::Server.new([route_handler, CORSHandler.new, JSONHandler.new])
# Do Unix bind() syscall, rescue errors
begin
server.bind_tcp(host, port)
rescue e : Errno
abort "Failed to bind() to #{host}:#{port}: #{e}"
end
puts "Rebase service bind() finished"
puts "Listening on http://#{host}:#{port}"
server.listen # Listen
end
end
# Run application
puts "Initializing Rebase API Server v#{VERSION}"
server = RebaseServer.new
# Routerize
puts "Setting up router routes"
server.routerize!
# Start HTTP server
if __FILE__.includes?("rebased")
puts "Binding HTTP service v#{API_VERSION}"
server.run
end