-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathapi.cgi
More file actions
executable file
·301 lines (280 loc) · 13.2 KB
/
api.cgi
File metadata and controls
executable file
·301 lines (280 loc) · 13.2 KB
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
use CGI;
use JSON;
use Conf;
use Data::Dumper;
use URI::Escape;
$CGI::LIST_CONTEXT_WARN = 0;
$CGI::Application::LIST_CONTEXT_WARN = 0;
# create cgi and json objects
my $cgi = new CGI;
my $json = new JSON;
$json = $json->utf8();
my %private_resources = (
'heartbeat' => 1,
'job' => 1,
'pipeline' => 1,
'resource' => 1,
'status' => 1,
'server' => 1,
'test' => 1,
'user' => 1,
'notebook' => 1
);
# get request method
$ENV{'REQUEST_METHOD'} =~ tr/a-z/A-Z/;
my $request_method = $ENV{'REQUEST_METHOD'};
if (lc($request_method) eq 'options') {
print $cgi->header(-Access_Control_Allow_Origin => '*',
-status => 200,
-type => 'text/plain',
-charset => 'UTF-8',
-Access_Control_Allow_Methods => 'POST, GET, OPTIONS, PUT, DELETE',
-Access_Control_Allow_Headers => 'AUTH, AUTHORIZATION, CONTENT-TYPE'
);
print "";
exit 0;
}
# get protocol
my $is_ssl = 0;
if ($cgi->http('X-Forwarded-Proto') && ($cgi->http('X-Forwarded-Proto') eq 'https')) {
$is_ssl = 1;
}
# get REST parameters
my $abs = $cgi->url(-relative=>1);
if ($abs !~ /\.cgi/) {
$abs = $cgi->url(-base=>1);
}
my $rest = $cgi->url(-path_info=>1);
$rest =~ s/^.*$abs\/?//;
$rest =~ s/^\///;
$rest =~ s/^\d+//;
$rest =~ s/^\///;
$rest =~ s/^api\.cgi//;
$rest =~ s/^\///;
my @rest_parameters = split m#/#, $rest;
map {$rest[$_] =~ s#forwardslash#/#gi} (0 .. $#rest);
# get the resource
my $resource = shift @rest_parameters;
# get resource list
my $resources = [];
my $resource_path = $Conf::api_resource_path;
if (! $resource_path) {
print $cgi->header(-type => 'text/plain',
-status => 500,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( {"ERROR"=> "resource directory not found"} );
exit 0;
}
if (opendir(my $dh, $resource_path)) {
my @res = grep { -f "$resource_path/$_" } readdir($dh);
closedir $dh;
@$resources = map { my ($r) = $_ =~ /^(.*)\.pm$/; $r ? $r: (); } grep { $_ =~ /^[a-zA-Z](.*)\.pm$/ } @res;
unless ($cgi->param('all')) {
@$resources = grep { ! exists($private_resources{$_}) } @$resources;
}
} else {
if ($cgi->param('POSTDATA') && ! $resource) {
print $cgi->header(-type => 'application/json',
-status => 200,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( { jsonrpc => "2.0",
id => undef,
error => { code => -32603,
message => "Internal error",
data => "resource directory offline" }
} );
exit 0;
} else {
print $cgi->header( -type => 'text/plain',
-status => 500,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( {"ERROR"=> "resource directory offline"} );
exit 0;
}
}
# check for json rpc
my $json_rpc = $cgi->param('POSTDATA') || $cgi->param('keywords');
# no resource, process as json rpc
if ($json_rpc && (! $resource)) {
$cgi->delete('POSTDATA');
my ($rpc_request, $submethod);
eval { $rpc_request = $json->decode($json_rpc) };
if ($@) {
print $cgi->header( -type => 'application/json',
-status => 200,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( { jsonrpc => "2.0",
id => undef,
error => { code => -32700,
message => "Parse error",
data => $@ }
} );
exit 0;
}
my $json_rpc_id = $rpc_request->{id};
my $params = $rpc_request->{params};
if (ref($params) eq 'ARRAY' && ref($params->[0]) eq 'HASH') {
$params = $params->[0];
}
unless (ref($params) eq 'HASH') {
print $cgi->header( -type => 'application/json',
-status => 200,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( { jsonrpc => "2.0",
id => undef,
error => { code => -32602,
message => "Invalid params",
data => "only named parameters are accepted" }
} );
exit 0;
}
foreach my $key (keys(%$params)) {
if ($key eq 'id') {
@rest_parameters = ( $params->{$key} );
} else {
$cgi->param($key, $params->{$key});
}
}
(undef, $request_method, $resource, $submethod) = $rpc_request->{method} =~ /^(\w+\.)?(get|post|delete|put)_(\w+)_(\w+)$/;
$json_rpc = 1;
}
# this is not json rpc, normal data POST
else {
$json_rpc = undef;
}
# check for authentication
my $user;
my $user_auth;
my $token;
if ($cgi->http('HTTP_AUTH') || $cgi->param('auth') || $cgi->http('HTTP_Authorization') || $cgi->param('authorization')) {
eval {
require Auth;
Auth->import();
my $message;
# get bearer / token
my $auth = $cgi->http('HTTP_AUTH') || $cgi->param('auth') || $cgi->http('HTTP_Authorization') || $cgi->param('authorization');
my @parts = split(/ /, $auth);
if (scalar(@parts) == 2) {
$user_auth = $parts[0];
$token = $parts[1];
} elsif (scalar(@parts) == 1) {
$user_auth = 'mgrast';
$token = $parts[0];
} else {
print $cgi->header( -type => 'application/json',
-status => 401,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( {"ERROR" => "authentication failed - invalid auth format"} );
exit 0;
}
# authenticate / get user
($user, $message) = Auth::authenticate($auth, $is_ssl);
unless($user) {
unless ($message eq "valid kbase user") {
print $cgi->header( -type => 'application/json',
-status => 401,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( {"ERROR" => "authentication failed - $message"} );
exit 0;
}
}
};
if ($@) {
print $cgi->header( -type => 'application/json',
-status => 401,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( {"ERROR"=> "authentication failed - $@"} );
exit 0;
}
}
# print google analytics
use GoogleAnalytics;
my $debug = undef;
if($user) {
GoogleAnalytics::track_page_view($user->_id, $debug);
} else {
GoogleAnalytics::track_page_view("anonymous", $debug);
}
# if a resource is passed, call the resources module
if ($resource) {
my $error = '';
my $package = $Conf::api_resource_dir."::".$resource;
{
no strict;
eval "require $package;";
$error = $@;
}
if ($error) {
print STDERR $error."\n";
print $cgi->header( -type => 'application/json',
-status => 500,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( {"ERROR"=> "resource '$resource' does not exist"} );
exit 0;
} else {
# check for kbase ids
if (scalar(@rest_parameters)) {
$rest_parameters[0] = uri_unescape($rest_parameters[0]);
$rest_parameters[0] =~ s/^kb\|(.+)$/$1/;
}
# create params hash
my $params= { 'rest_parameters' => \@rest_parameters,
'method' => $request_method,
'user' => $user,
'token' => $token,
'user_auth' => $user_auth,
'json_rpc' => $json_rpc,
'json_rpc_id' => $json_rpc_id,
'submethod' => $submethod,
'cgi' => $cgi,
'resource' => $resource,
'is_ssl' => $is_ssl
};
eval {
my $resource_obj = $package->new($params);
$resource_obj->request();
};
if ($@) {
print $cgi->header( -type => 'text/plain',
-status => 500,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode( {"ERROR"=> "resource request failed\n$@\n"} );
exit 0;
}
}
}
# we are called without a resource, return API information
else {
my $cgi_url = $Conf::url_base ? $Conf::url_base : $cgi->url;
if ($is_ssl) {
$cgi_url =~ s/^http/https/;
}
$cgi_url =~ s/^(.*)\/$/$1/;
$cgi_url =~ s/^(.*)\/api.cgi$/$1/;
my @res = map {{ 'name' => $_, 'url' => $cgi_url.'/'.$_ , 'documentation' => $cgi_url.'/api.html#'.$_}} sort @$resources;
my $content = { version => 1,
service => 'MG-RAST',
url => $cgi_url,
documentation => $cgi_url.'/api.html',
description => "<p>The MG-RAST API covers most of the functionality available through the MG-RAST website, with access to annotations, analyses, metadata and access to the MG-RAST user inbox to view contents as well as upload files. All sequence data and data products from intermediate stages in the analysis pipeline are available for download. Other resources provide services not available through the website, e.g. the m5nr resource lets you query the m5nr database.</p><p>Each query to the API is represented as a URI beginning with \"http://api.mg-rast.org/\" and has a defined structure to pass the requests and parameters to the API server.</p><p>The URI queries can be used from the command line, e.g. using curl, in a browser, or incorporated in a shell script or program.</p><p>Each URI has the form</p><pre>\"http://api.mg-rast.org/{version}/{resourcepath}?{querystring}\"</pre><p>The {version} value (currently '1') explicitly directs the request to a specific version of the API, if it is omitted the latest API version will be used.</p><p>The resource path is constructed from the path parameters listed below to define a specific resource and the optional query string is used to filter the results obtained for the resource. For example:</p><pre>http://api.mg-rast.org/1/annotation/sequence/mgm4447943.3?evalue=10&type=organism&source=SwissProt</pre><p>In this example the resource path \"annotation/sequence/mgm4447943.3\" defines a request for the annotated sequences for the MG-RAST job with ID 4447943.3.</p><p>The optional query string \"evalue=10&type=organism&source=SwissProt\" modifies the results by setting an evalue cutoff, annotation type and database source.</p><p>The API provides an authentication mechanism for access to private MG-RAST jobs and users' inbox. The 'auth_key' (or 'webkey') is a 25 character long string (e.g. 'j6FNL61ekNarTgqupMma6eMx5') which is used by the API to identify an MG-RAST user account and determine access rights to metagenomes. Note that the auth_key is valid for a limited time after which queries using the key will be rejected. You can create a new auth_key or view the expiration date and time of an existing auth_key on the MG-RAST website. An account can have only one valid auth_key and creating a new key will invalidate an existing key.</p><p>All public data in MG-RAST is available without an auth_key. All API queries for private data which either do not have an auth_key or use an invalid or expired auth_key will get a \"insufficient permissions to view this data\" response.</p><p>The auth_key can be included in the query string like:</p><pre>\nhttp://api.mg-rast.org/1/annotation/sequence/mgm4447943.3?evalue=10&type=organism&source=SwissProt&auth=j6FNL61ekNarTgqupMma6eMx5</pre><p>or in a request using curl like:</p><pre>curl -X GET -H \"auth: j6FNL61ekNarTgqupMma6eMx5\" \"http://api.mg-rast.org/1/annotation/sequence/mgm4447943.3?evalue=10&type=organism&source=SwissProt\"</pre><p>Note that for the curl command the quotes are necessary for the query to be passed to the API correctly.</p><p>If an optional parameter passed through the query string has a list of values only the first will be used. When multiple values are required, e.g. for multiple md5 checksum values, they can be passed to the API like:</p><pre>curl -X POST -d '{\"data\":[\"000821a2e2f63df1a3873e4b280002a8\",\"15bf1950bd9867099e72ea6516e3d602\"]}' \"http://api.mg-rast.org/m5nr/md5\"</pre><p>In some cases, the data requested is in the form of a list with a large number of entries. In these cases the limit and offset parameters can be used to step through the list, for example:</p><pre>http://api.mg-rast.org/1/project?order=name&limit=20&offset=100</pre><p>will limit the number of entries returned to 20 with an offset of 100. If these parameters are not provided default values of limit=10 and offset=0 are used. The returned JSON structure will contain the 'next' and 'prev' (previous) URIs to simplify stepping through the list.</p><p>The data returned may be plain text, compressed gzipped files or a JSON structure.</p><p>Most API queries are 'synchronous' and results are returned immediately. Some queries may require a substantial time to compute results, in these cases you can select the asynchronous option by adding '&asynchronous=1' to the end of the query string. This query will then return a URL which will return the query results when they are ready.</p>",
resources => \@res };
print $cgi->header(-type => 'application/json',
-status => 200,
-charset => 'UTF-8',
-Access_Control_Allow_Origin => '*' );
print $json->encode($content);
exit 0;
}
sub TO_JSON { return { %{ shift() } }; }
1;