|
1
|
|
|
require 'midb/server_controller' |
|
2
|
|
|
require 'midb/server_model' |
|
3
|
|
|
require 'midb/server_view' |
|
4
|
|
|
require 'midb/errors_view' |
|
5
|
|
|
require 'midb/security_controller' |
|
6
|
|
|
require 'midb/hooks' |
|
7
|
|
|
|
|
8
|
|
|
require 'yaml' |
|
9
|
|
|
require 'socket' |
|
10
|
|
|
require 'uri' |
|
11
|
|
|
require 'json' |
|
12
|
|
|
require 'sqlite3' |
|
13
|
|
|
|
|
14
|
|
|
module MIDB |
|
15
|
|
|
module API |
|
16
|
|
|
# @author unrar |
|
17
|
|
|
# This class handles runs the server engine using sockets and a loop. |
|
18
|
|
|
class Engine |
|
19
|
|
|
# Attribute declaration here |
|
20
|
|
|
# @!attribute config |
|
21
|
|
|
# @return [Hash] Contains the project's configuration, saved in .midb.yaml |
|
22
|
|
|
# @!attribute db |
|
23
|
|
|
# @return [String] Database name (if SQLite is the engine, file name without extension) |
|
24
|
|
|
# @!attribute http_status |
|
25
|
|
|
# @return [String] HTTP status code and string representation for the header |
|
26
|
|
|
# @!attribute h |
|
27
|
|
|
# @return [Object] MIDB::API::Hooks instance |
|
28
|
|
|
attr_accessor :config, :db, :http_status, :hooks |
|
29
|
|
|
|
|
30
|
|
|
# Handle an unauthorized request |
|
31
|
|
|
def unauth_request |
|
32
|
|
|
@http_status = "401 Unauthorized" |
|
33
|
|
|
MIDB::Interface::Server.info(:no_auth) |
|
34
|
|
|
MIDB::Interface::Server.json_error(401, "Unauthorized").to_json |
|
35
|
|
|
end |
|
36
|
|
|
|
|
37
|
|
|
# Constructor |
|
38
|
|
|
# |
|
39
|
|
|
# @param db [String] Database to which the server will bind. |
|
40
|
|
|
# @param stat [Fixnum] HTTP Status |
|
41
|
|
|
# @param cnf [Hash] Config from the server controller. |
|
42
|
|
|
def initialize(db, stat, cnf, hooks=nil) |
|
43
|
|
|
@config = cnf |
|
44
|
|
|
@db = db |
|
45
|
|
|
@http_status = stat |
|
46
|
|
|
if hooks == nil |
|
47
|
|
|
@hooks = MIDB::API::Hooks.new |
|
48
|
|
|
else |
|
49
|
|
|
@hooks = hooks |
|
50
|
|
|
end |
|
51
|
|
|
end |
|
52
|
|
|
|
|
53
|
|
|
# Starts the server on a given port (default: 8081) |
|
54
|
|
|
# |
|
55
|
|
|
# @param port [Fixnum] Port to which the server will listen. |
|
56
|
|
|
def start(port=8081) |
|
57
|
|
|
serv = TCPServer.new("localhost", port) |
|
58
|
|
|
MIDB::Interface::Server.info(:start, port) |
|
59
|
|
|
|
|
60
|
|
|
# Manage the requests |
|
61
|
|
|
loop do |
|
62
|
|
|
socket = serv.accept |
|
63
|
|
|
MIDB::Interface::Server.info(:incoming_request, socket.addr[3]) |
|
64
|
|
|
|
|
65
|
|
|
request = self.parse_request(socket.gets) |
|
66
|
|
|
|
|
67
|
|
|
# Get a hash with the headers |
|
68
|
|
|
headers = {} |
|
69
|
|
|
while line = socket.gets.split(' ', 2) |
|
|
|
|
|
|
70
|
|
|
break if line[0] == "" |
|
71
|
|
|
headers[line[0].chop] = line[1].strip |
|
72
|
|
|
end |
|
73
|
|
|
data = socket.read(headers["Content-Length"].to_i) |
|
74
|
|
|
|
|
75
|
|
|
|
|
76
|
|
|
MIDB::Interface::Server.info(:request, request) |
|
77
|
|
|
response_json = Hash.new() |
|
78
|
|
|
|
|
79
|
|
|
# Endpoint syntax: ["", FILE, ID, (ACTION)] |
|
80
|
|
|
endpoint = request[1].split("/") |
|
81
|
|
|
ep_file = endpoint[1].split("?")[0] |
|
82
|
|
|
|
|
83
|
|
|
method = request[0] |
|
84
|
|
|
endpoints = [] # Valid endpoints |
|
85
|
|
|
|
|
86
|
|
|
# Load the JSON served files |
|
87
|
|
|
@config["serves"].each do |js| |
|
88
|
|
|
# The filename is a valid endpoint |
|
89
|
|
|
endpoints.push File.basename(js, ".*") |
|
90
|
|
|
end |
|
91
|
|
|
|
|
92
|
|
|
# Load the endpoints |
|
93
|
|
|
found = false |
|
94
|
|
|
endpoints.each do |ep| |
|
95
|
|
|
if ep_file == ep |
|
96
|
|
|
found = true |
|
97
|
|
|
MIDB::Interface::Server.info(:match_json, ep) |
|
98
|
|
|
# Create the model |
|
99
|
|
|
dbop = MIDB::API::Model.new(ep, @db, self) |
|
100
|
|
|
# Analyze the request and pass it to the model |
|
101
|
|
|
# Is the method accepted? |
|
102
|
|
|
accepted_methods = ["GET", "POST", "PUT", "DELETE"] |
|
103
|
|
|
unless accepted_methods.include? method |
|
104
|
|
|
@http_status = "405 Method Not Allowed" |
|
105
|
|
|
response_json = MIDB::Interface::Server.json_error(405, "Method Not Allowed").to_json |
|
106
|
|
|
else |
|
107
|
|
|
# Do we need authentication? |
|
108
|
|
|
auth_req = false |
|
109
|
|
|
unauthenticated = false |
|
110
|
|
|
if @config["privacy#{method.downcase}"] == true |
|
111
|
|
|
MIDB::Interface::Server.info(:auth_required) |
|
112
|
|
|
auth_req = true |
|
113
|
|
|
# If it's a GET request and we have a different key for GET methods... |
|
114
|
|
|
if method == "GET" |
|
115
|
|
|
data = ep_file |
|
116
|
|
|
end |
|
117
|
|
|
if (@config["apigetkey"] != nil) && (method == "GET") |
|
118
|
|
|
unauthenticated = (not headers.has_key? "Authentication") || |
|
119
|
|
|
(not MIDB::API::Security.check?(headers["Authentication"], data, @config["apigetkey"])) |
|
120
|
|
|
else |
|
121
|
|
|
unauthenticated = (not headers.has_key? "Authentication") || |
|
122
|
|
|
(not MIDB::API::Security.check?(headers["Authentication"], data, @config["apikey"])) |
|
123
|
|
|
end |
|
124
|
|
|
end |
|
125
|
|
|
# Proceed to handle the request |
|
126
|
|
|
if unauthenticated |
|
127
|
|
|
response_json = self.unauth_request |
|
128
|
|
|
else |
|
129
|
|
|
MIDB::Interface::Server.info(:auth_success) if (not unauthenticated) && auth_req |
|
130
|
|
|
if method == "GET" |
|
131
|
|
|
case endpoint.length |
|
132
|
|
|
when 2 |
|
133
|
|
|
# No ID has been specified. Return all the entries |
|
134
|
|
|
# Pass it to the model and get the JSON |
|
135
|
|
|
response_json = dbop.get_all_entries().to_json |
|
136
|
|
|
when 3 |
|
137
|
|
|
# An ID has been specified. Should it exist, return all of its entries. |
|
138
|
|
|
response_json = dbop.get_entries(endpoint[2]).to_json |
|
139
|
|
|
end |
|
140
|
|
|
elsif method == "POST" |
|
141
|
|
|
response_json = dbop.post(data).to_json |
|
142
|
|
|
else |
|
143
|
|
|
if endpoint.length >= 3 |
|
144
|
|
|
if method == "DELETE" |
|
145
|
|
|
response_json = dbop.delete(endpoint[2]).to_json |
|
146
|
|
|
elsif method == "PUT" |
|
147
|
|
|
response_json = dbop.put(endpoint[2], data).to_json |
|
148
|
|
|
end |
|
149
|
|
|
else |
|
150
|
|
|
@http_status = "404 Not Found" |
|
151
|
|
|
response_json = MIDB::Interface::Server.json_error(404, "Must specify an ID.").to_json |
|
152
|
|
|
end |
|
153
|
|
|
end |
|
154
|
|
|
end |
|
155
|
|
|
end |
|
156
|
|
|
MIDB::Interface::Server.info(:response, response_json) |
|
157
|
|
|
# Return the results via HTTP |
|
158
|
|
|
socket.print "HTTP/1.1 #{@http_status}\r\n" + |
|
159
|
|
|
"Content-Type: text/json\r\n" + |
|
160
|
|
|
"Content-Length: #{response_json.size}\r\n" + |
|
161
|
|
|
"Connection: close\r\n" |
|
162
|
|
|
socket.print "\r\n" |
|
163
|
|
|
socket.print response_json |
|
164
|
|
|
socket.print "\r\n" |
|
165
|
|
|
MIDB::Interface::Server.info(:success) |
|
166
|
|
|
end |
|
167
|
|
|
end |
|
168
|
|
|
unless found |
|
169
|
|
|
MIDB::Interface::Server.info(:not_found) |
|
170
|
|
|
response = MIDB::Interface::Server.json_error(404, "Invalid API endpoint.").to_json |
|
171
|
|
|
|
|
172
|
|
|
socket.print "HTTP/1.1 404 Not Found\r\n" + |
|
173
|
|
|
"Content-Type: text/json\r\n" + |
|
174
|
|
|
"Content-Length: #{response.size}\r\n" + |
|
175
|
|
|
"Connection: close\r\n" |
|
176
|
|
|
socket.print "\r\n" |
|
177
|
|
|
socket.print response |
|
178
|
|
|
end |
|
179
|
|
|
end |
|
180
|
|
|
end |
|
181
|
|
|
|
|
182
|
|
|
# Method: parse_request |
|
183
|
|
|
# Parses an HTTP requests and returns an array [method, uri] |
|
184
|
|
|
def parse_request(req) |
|
185
|
|
|
[req.split(" ")[0], req.split(" ")[1]] |
|
186
|
|
|
end |
|
187
|
|
|
end |
|
188
|
|
|
end |
|
189
|
|
|
end |