-
Notifications
You must be signed in to change notification settings - Fork 239
/
Copy pathserver.cpp
150 lines (138 loc) · 5.81 KB
/
server.cpp
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
#include "Simple-Web-Server/server_http.hpp"
#include "external/sqlite_modern_cpp.h"
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
using namespace std;
namespace po = boost::program_options;
using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;
#include <iostream>
#include <fstream>
inline unsigned char from_hex (unsigned char ch) {
if (ch <= '9' && ch >= '0')
ch -= '0';
else if (ch <= 'f' && ch >= 'a')
ch -= 'a' - 10;
else if (ch <= 'F' && ch >= 'A')
ch -= 'A' - 10;
else
ch = 0;
return ch;
}
const std::string urldecode (const std::string& str) {
string result;
string::size_type i;
for (i = 0; i < str.size(); ++i)
{
if (str[i] == '+')
{
result += ' ';
}
else if (str[i] == '%' && str.size() > i+2)
{
const unsigned char ch1 = from_hex(str[i+1]);
const unsigned char ch2 = from_hex(str[i+2]);
const unsigned char ch = (ch1 << 4) | ch2;
result += ch;
i += 2;
}
else
{
result += str[i];
}
}
return result;
}
int main(int argc, char* argv[]) {
string input;
string staticPath;
unsigned port;
po::options_description desc("tilemaker-server\nServe tiles from an .mbtiles archive\n\nAvailable options");
desc.add_options()
("help","show help message")
("input",po::value< string >(&input),"source .mbtiles")
("static", po::value< string >(&staticPath)->default_value("static"), "path of static files")
("port",po::value< unsigned >(&port)->default_value(8080), "port to serve tiles");
po::positional_options_description p;
p.add("input", -1);
po::variables_map vm;
try {
po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
} catch (const po::unknown_option& ex) {
cerr << "Unknown option: " << ex.get_option_name() << endl;
return -1;
}
po::notify(vm);
if (vm.count("help")) { std::cout << desc << std::endl; return 1; }
if (vm.count("input") == 0) { std::cerr << "You must specify an .mbtiles file. Run with --help to find out more." << std::endl; return -1; }
HttpServer server;
server.config.port = port;
sqlite::database db;
db.init(input);
cout << "Starting local server on port " << server.config.port << endl;
server.resource["^/([0-9]+)/([0-9]+)/([0-9]+).pbf$"]["GET"] = [&db](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
int32_t zoom = stoi(request->path_match[1]);
int32_t col = stoi(request->path_match[2]);
int32_t y = stoi(request->path_match[3]);
vector<char> pbfBlob;
int tmsY = pow(2,zoom) - 1 - y;
db << "SELECT tile_data FROM tiles WHERE zoom_level=? AND tile_column=? AND tile_row=?" << zoom << col << tmsY >> pbfBlob;
std::string outStr(pbfBlob.begin(), pbfBlob.end());
SimpleWeb::CaseInsensitiveMultimap header;
header.emplace("Content-Encoding", "gzip");
header.emplace("Access-Control-Allow-Origin", "*");
response->write(outStr,header);
};
server.resource["^/metadata$"]["GET"] = [&db](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
rapidjson::Document document;
document.SetObject();
rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
db << "SELECT name, value FROM metadata;" >> [&](string name, string value) {
if (name == "json") {
rapidjson::Document subDocument;
subDocument.Parse(value.c_str());
document.AddMember("json",subDocument,allocator);
} else {
rapidjson::Value nameVal;
nameVal.SetString(name.c_str(), allocator);
rapidjson::Value valueVal;
valueVal.SetString(value.c_str(), allocator);
document.AddMember(nameVal, valueVal, allocator);
}
};
rapidjson::StringBuffer stringbuf;
rapidjson::Writer<rapidjson::StringBuffer> writer(stringbuf);
document.Accept(writer);
response->write(stringbuf.GetString());
};
server.default_resource["GET"] = [&staticPath](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
try {
auto pathstr = urldecode(request->path);
if (pathstr == "/") pathstr = "/index.html";
auto web_root_path = boost::filesystem::canonical(staticPath);
auto path = boost::filesystem::canonical(web_root_path / pathstr);
if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||
!equal(web_root_path.begin(), web_root_path.end(), path.begin()))
throw invalid_argument("path must be within root path");
SimpleWeb::CaseInsensitiveMultimap header;
auto ifs = make_shared<ifstream>();
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
if(*ifs) {
auto length = ifs->tellg();
ifs->seekg(0, ios::beg);
header.emplace("Content-Length", to_string(length));
response->write(header);
vector<char> buffer(length);
ifs->read(&buffer[0],length);
response->write(&buffer[0],length);
} else {
throw invalid_argument("could not read file");
}
} catch(const exception &e) {
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path " + request->path + ": " + e.what());
}
};
server.start();
}