Skip to content

Commit 9227a18

Browse files
authored
Merge pull request #4 from steve-krisjanovs/master
Improved BLOB processing!
2 parents c5c9a45 + a794af7 commit 9227a18

2 files changed

Lines changed: 171 additions & 9 deletions

File tree

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Options:
2626
(a number)
2727
--cors: CORS URLs to allow requests from
2828
(default: [])
29+
--requestlimit: request body limit for HTTP POSTs
30+
(default: "1mb")
2931
```
3032

3133
# Usage examples
@@ -117,6 +119,48 @@ $ curl -H "Content-Type: application/json" -d '{"sql":"select DATETIME(?) AS UTC
117119
[{"UTC_ISO":"2020-09-10 02:06:02"}]
118120
```
119121

122+
## BLOB Handling
123+
124+
Blobs via http POST/GET can be treated as byte arrays or base64-encoded text. This is handled via the blobtype variable in the HTTP GET request, or a blobtype object member in the request body for an HTTP POST. Allowable values for blobtype are "base64" and "array". If blobtype is omitted, "base64" is the default.
125+
126+
This also affects how your parameterized SQLite statements are sent to the server. BLOB query parameters must be structured as {"data": value}, whereas other parameter types (e.g. text, numberic) are treated as primitives in the params array (see exables below).
127+
128+
```
129+
# EXAMPLES
130+
###############################################################################
131+
132+
GET http://localhost:2048?sql=select BLOB_FIELD from BLOB_TABLE&blobtype=base64
133+
>> returns BLOB fields as base64 strings
134+
135+
###############################################################################
136+
137+
GET http://localhost:2048?sql=select BLOB_FIELD from BLOB_TABLE&blobtype=array
138+
>> returns BLOB fields as Buffer objects
139+
140+
###############################################################################
141+
142+
POST (application/json) http://localhost:2048
143+
BODY:
144+
{
145+
"blobtype": "base64",
146+
"sql": "insert into BLOB_TABLE(KEY,BLOB_FIELD) values (?,?)",
147+
"params": [1,{"data": "base64string"}]
148+
}
149+
>> base64 data is automatically converted to buffers before inserting the record
150+
151+
###############################################################################
152+
153+
POST (application/json) http://localhost:2048
154+
BODY:
155+
{
156+
"blobtype": "array",
157+
"sql": "insert into BLOB_TABLE(KEY,BLOB_FIELD) values (?,?)",
158+
"params": [1,{"data": bytearray[]}]
159+
}
160+
>> byte data is converted to buffer before inserting the record
161+
162+
```
163+
120164
## CORS
121165

122166
```console

main.js

Lines changed: 127 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ flags.defineString("db", "", "DB File path");
55
flags.defineBoolean("readonly", false, "Open the database for readonly");
66
flags.defineNumber("port", 2048, "TCP Port to listen on");
77
flags.defineMultiString("cors", [], "CORS URLs to allow requests from");
8+
flags.defineString("requestlimit", "1mb", "request body limit for HTTP POSTs");
89
flags.parse();
910

1011
console.log("db", "=", flags.get("db"));
1112
console.log("readonly", "=", flags.get("readonly"));
1213
console.log("port", "=", flags.get("port"));
1314
console.log("cors", "=", flags.get("cors").join(", ") || "false");
15+
console.log("requestlimit", "=", flags.get("requestlimit"))
1416

1517
const Database = require("better-sqlite3");
1618

@@ -19,7 +21,7 @@ const bodyParser = require("body-parser");
1921

2022
const app = express();
2123
app.use(require("compression")());
22-
app.use(bodyParser.urlencoded({ extended: false, limit: "1mb" }));
24+
app.use(bodyParser.urlencoded({ extended: false, limit: flags.get("requestlimit") }));
2325
app.use(bodyParser.json({ limit: "1mb" }));
2426
app.use(function (req, res, next) {
2527
req.connection.setTimeout(2 * 60 * 1000); // 2 minutes
@@ -45,35 +47,123 @@ if (flags.get("cors").length > 0) {
4547

4648
function getSqlExecutor(httpRequestFieldName) {
4749
return function (req, res) {
50+
51+
let blobtype = req[httpRequestFieldName].blobtype;
52+
if (blobtype == undefined || blobtype == null)
53+
{
54+
blobtype = "base64"; //default http blob handling to base64 if blobtype queryparam/bodyparam is missing
55+
}
56+
if ((typeof blobtype) != "string")
57+
{
58+
res.status(400);
59+
res.send(
60+
`${res.statusCode}: 'blobtype' element mandatory in http request ${httpRequestFieldName} must be string value 'base64' or 'array'!\n`
61+
);
62+
return;
63+
}
64+
blobtype = blobtype.toLowerCase().trim();
65+
if (blobtype != "base64" && blobtype != "array")
66+
{
67+
res.status(400);
68+
res.send(
69+
`${res.statusCode}: 'blobtype' element mandatory in http request ${httpRequestFieldName} must be string value 'base64' or 'array'!\n`
70+
);
71+
return;
72+
}
73+
4874
const sql = req[httpRequestFieldName].sql;
4975
let params = [];
5076
if (httpRequestFieldName === "body" && req.is("application/json")) {
5177
params = req[httpRequestFieldName].params;
5278
if (params == undefined || params == null) {
5379
params = [];
5480
}
55-
}
56-
if (!sql) {
57-
return res.send([]);
58-
}
5981

60-
let db;
61-
try {
6282
if (!Array.isArray(params)) {
6383
res.status(400);
6484
res.send(
65-
`${err.code}: 'params' element in http request body must be an array!\n`
85+
`${res.statusCode}: 'params' element in http request body must be an array!\n`
6686
);
6787
return;
6888
}
89+
90+
if (blobtype === "base64") {
91+
/**********************************************************************************
92+
Enumerate through sqlite parameters and if of them is a blob param
93+
then decode+convert that param to a buffer.
94+
- Base64 Blob parameter is structured as follows: {data: "base64data"},
95+
- Non-blob parameters are primitives. not objects (numeric,string,bool)
96+
***********************************************************************************/
97+
for (let i = 0; i < params.length; i++) {
98+
let param = params[i];
99+
//if the parameter is an object, assume it's a blob parameter
100+
if (typeof param === 'object' && param !== null) {
101+
102+
if (param.hasOwnProperty("data"))
103+
{
104+
var data = param.data;
105+
let buff = null;
106+
if (typeof Buffer.from === "function") {
107+
// Node 5.10+
108+
buff = Buffer.from(data, 'base64');
109+
}
110+
else {
111+
// older Node versions, now deprecated
112+
buff = new Buffer(data, 'base64');
113+
}
114+
params[i] = buff;
115+
}
116+
}
117+
}
118+
}
119+
else if (blobtype === "array") {
120+
/**********************************************************************************
121+
Enumerate through sqlite parameters and if of them is a blob param
122+
then convert the param to a buffer.
123+
- Array Blob parameter is structured as follows: {data: bytearray[]},
124+
- Non-blob parameters are primitives. not objects (numeric,string,bool)
125+
***********************************************************************************/
126+
for (let i = 0; i < params.length; i++) {
127+
let param = params[i];
128+
//if the parameter is an object, assume it's a blob parameter
129+
if (typeof param === 'object' && param !== null) {
130+
if (param.hasOwnProperty("data"))
131+
{
132+
var data = param.data;
133+
let buff = null;
134+
if (typeof Buffer.from === "function") {
135+
// Node 5.10+
136+
buff = Buffer.from(data);
137+
}
138+
else {
139+
// older Node versions, now deprecated
140+
buff = new Buffer(data);
141+
}
142+
params[i] = buff;
143+
}
144+
}
145+
}
146+
}
147+
}
148+
if (!sql) {
149+
return res.send([]);
150+
}
151+
152+
let db;
153+
try {
69154
const readonly = flags.get("readonly");
70155
db = new Database(flags.get("db"), { readonly });
71156
if (!readonly) {
72157
db.pragma("journal_mode = WAL");
73158
}
74159
} catch (err) {
75160
res.status(400);
76-
res.send(`${err.code}: ${err.message}\n`);
161+
//precautionary check if err doesn't have a code member
162+
let errcode = res.statusCode;
163+
if (err.code) {
164+
errcode = err.code;
165+
}
166+
res.send(`${errcode}: ${err.message}\n`);
77167
db.close && db.close();
78168
return;
79169
}
@@ -95,6 +185,34 @@ function getSqlExecutor(httpRequestFieldName) {
95185
}
96186

97187
db.close();
188+
189+
//if blobtype = base64, enumerate through the rows/fields and convert any buffer fields found to base64 string
190+
if (blobtype == "base64")
191+
{
192+
for (let i = 0; i < rows.length; i++) {
193+
let row = rows[i];
194+
195+
//get field count for row
196+
let fieldcount = 0;
197+
for (var prop in row) {
198+
if (Object.prototype.hasOwnProperty.call(row, prop)) {
199+
fieldcount ++;
200+
}
201+
}
202+
203+
//enumerate through fields
204+
for (let j = 0; j < fieldcount; j++)
205+
{
206+
let fieldname = Object.keys(row)[j];
207+
let fielddata = row[fieldname];
208+
if (Buffer.isBuffer(fielddata)) {
209+
let base64data = fielddata.toString('base64');
210+
rows[i][fieldname] = base64data;
211+
}
212+
}
213+
}
214+
}
215+
98216
res.send(rows);
99217
};
100218
}

0 commit comments

Comments
 (0)