-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.ts
166 lines (154 loc) · 4.38 KB
/
server.ts
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
import dotenv from 'dotenv';
dotenv.config();
import http from 'http';
import https from 'https';
import fs from 'fs';
import IPFS from 'ipfs';
const node = new IPFS({
config: {
Bootstrap: [
'/dns4/bootstrap.commontheory.io/tcp/8001/ipfs/QmQynHVRAVwcP3nsGW9s8Y1hLXN1Lc6a3WEasmC6iAxZBr'
]
}
});
node.on('ready', async () => {
console.log('IPFS node ready');
preloadAddresses();
});
/**
* The IPNS address of a json file that stores domain hostnames keyed to IPFS
* hashes.
*
* Defaults to commontheory mappings.
**/
const MAPPING_ADDRESS = process.env.MAPPING_ADDRESS || '/ipns/QmcizC46HXX5aqFw1z7xvvAN4YqMhgZB5H7pKv5Mfpr1TJ';
// Cache some IPFS files in memory
const cache: {
[key: string]: string
} = {};
// Store the order cache keys are added
const cacheKeys: string[] = [];
const MAX_CACHE_LENGTH = 20;
/**
* Check the mappings for the request host and server ipfs addressed file.
*
* Currently marks content-type as text/html for everything.
* TODO: Parse file mimetype somehow? Expand mapping file format?
**/
const serverHandler = async (req: http.IncomingMessage, res: http.ServerResponse) => {
const ipfsAddress = (await mappings())[req.headers.host];
if (!ipfsAddress) {
res.writeHead(404, {
'Content-Type': 'text/plain',
});
res.end(`No mapping specified for host ${req.headers.host}`);
return;
}
try {
const data = await download(ipfsAddress);
res.writeHead(200, {
'Content-Type': 'text/html',
});
res.end(data);
} catch (err) {
res.writeHead(500);
res.end('Error retrieving ipfs data' + err);
}
};
/**
* A 301 redirect to the https address.
**/
const redirectHandler = async (req: http.IncomingMessage, res: http.ServerResponse) => {
res.writeHead(301, {
'Location': `https://${req.headers.host}${req.url}`,
});
res.end();
};
/**
* The http server that proxies requests to IPFS resources. Mapping are done by
* hostname -> ipfs address. These are stored in the mappings.json file.
**/
http.createServer(process.env.REDIRECT_HTTPS ? redirectHandler : serverHandler).listen(3000, () => {
console.log('http server listening on port 3000');
});
/**
* The https server (if enabled)
**/
if (process.env.ENABLE_HTTPS) {
if (!process.env.HTTPS_CERT) {
throw new Error('https enabled without cert');
}
if (!process.env.HTTPS_PRIVATE_KEY) {
throw new Error('https enabled without private key');
}
const options = {
key: fs.readFileSync(process.env.HTTPS_PRIVATE_KEY),
cert: fs.readFileSync(process.env.HTTPS_CERT),
ca: process.env.HTTPS_CERT_CA && fs.readFileSync(process.env.HTTPS_CERT_CA)
};
https.createServer(options, serverHandler).listen(3001, () => {
console.log('https server listening on port 3001');
});
}
/**
* Load all IPFS addresses specified in the currently loaded mapping.
**/
async function preloadAddresses() {
const _mappings = await mappings();
Object.keys(_mappings).forEach(async (key) => {
const address = _mappings[key];
try {
await download(address);
console.log(`Successfully pre-loaded ${key}`);
} catch (err) {
console.log(`Error pre-loading ${key}: ${err}`);
}
});
}
/**
* Attempts to load a given ipfs address. Results are stored in the cache.
**/
async function download(ipfsAddress: string) {
if (cache[ipfsAddress]) {
return cache[ipfsAddress];
}
const files = await node.files.get(ipfsAddress);
if (files.length < 1) {
console.log('No files found for address');
return;
} else if (files.length > 1) {
console.log('Did not find single file at address');
return;
}
const data = files[0].content.toString('utf8');
// Add to cache
cacheKeys.unshift(ipfsAddress);
if (cacheKeys.length > MAX_CACHE_LENGTH) {
delete cache[cacheKeys.pop()];
}
cache[ipfsAddress] = data;
return data;
}
/**
* TODO: Use jsipfs ipns resolution in favor of maintaining two parallel
* processes at the Dockerfile level
**/
async function mappings(): Promise<{
[key: string]: string
}> {
let rawData = '';
return new Promise((rs, rj) => {
http.get(`http://localhost:8080${MAPPING_ADDRESS}`, (res) => {
if (res.statusCode !== 200) {
rj(new Error(`Request failed with status code: ${res.statusCode}`));
return;
}
res.on('data', (chunk) => {
rawData += chunk;
});
res.on('end', () => {
rs(JSON.parse(rawData));
});
});
}) as any;
}