tensorflow/inference/docker/build_artifacts/sagemaker_neuron/tensorflowServing.js (187 lines of code) (raw):
var tfs_base_uri = '/tfs/v1/models/'
var custom_attributes_header = 'X-Amzn-SageMaker-Custom-Attributes'
function invocations(r) {
var ct = r.headersIn['Content-Type']
if ('application/json' == ct || 'application/jsonlines' == ct || 'application/jsons' == ct) {
json_request(r)
} else if ('text/csv' == ct) {
csv_request(r)
} else {
return_error(r, 415, 'Unsupported Media Type: ' + (ct || 'Unknown'))
}
}
function ping(r) {
var uri = make_tfs_uri(r, false)
function callback (reply) {
if (reply.status == 200 && reply.responseText.includes('"AVAILABLE"')) {
r.return(200)
} else {
r.error('failed ping' + reply.responseText)
r.return(502)
}
}
r.subrequest(uri, callback)
}
function ping_without_model(r) {
// hack for TF 1.11 and MME
// for TF 1.11, send an arbitrary fixed request to the default model.
// if response is 400, the model is ok (but input was bad), so return 200
// for MME, the default model name is None and does not exist
// also return 200 in unlikely case our request was really valid
var uri = make_tfs_uri(r, true)
var options = {
method: 'POST',
body: '{"instances": "invalid"}'
}
function callback (reply) {
if (reply.status == 200 || reply.status == 400 ||
reply.responseText.includes('Servable not found for request: Latest(None)')) {
r.return(200)
} else {
r.error('failed ping' + reply.responseText)
r.return(502)
}
}
r.subrequest(uri, options, callback)
}
function return_error(r, code, message) {
if (message) {
r.return(code, '{"error": "' + message + '"}')
} else {
r.return(code)
}
}
function tfs_json_request(r, json) {
var uri = make_tfs_uri(r, true)
var options = {
method: 'POST',
body: json
}
var accept = r.headersIn.Accept
function callback (reply) {
var body = reply.responseText
if (reply.status == 400) {
// "fix" broken json escaping in \'instances\' message
body = body.replace("\\'instances\\'", "'instances'")
}
if (accept != undefined) {
var content_types = accept.trim().replace(" ", "").split(",")
if (content_types.includes('application/jsonlines') || content_types.includes('application/json')) {
body = body.replace(/\n/g, '')
r.headersOut['Content-Type'] = content_types[0]
}
}
r.return(reply.status, body)
}
r.subrequest(uri, options, callback)
}
function make_tfs_uri(r, with_method) {
var attributes = parse_custom_attributes(r)
var uri = tfs_base_uri + attributes['tfs-model-name']
if ('tfs-model-version' in attributes) {
uri += '/versions/' + attributes['tfs-model-version']
}
if (with_method) {
uri += ':' + (attributes['tfs-method'] || 'predict')
}
return uri
}
function parse_custom_attributes(r) {
var attributes = {}
var kv_pattern = /tfs-[a-z\-]+=[^,]+/g
var header = r.headersIn[custom_attributes_header]
if (header) {
var matches = header.match(kv_pattern)
if (matches) {
for (var i = 0; i < matches.length; i++) {
var kv = matches[i].split('=')
if (kv.length === 2) {
attributes[kv[0]] = kv[1]
}
}
}
}
// for MME invocations, tfs-model-name is in the uri, or use default_tfs_model
if (!attributes['tfs-model-name']) {
var uri_pattern = /\/models\/[^,]+\/invoke/g
var model_name = r.uri.match(uri_pattern)
if (model_name[0]) {
model_name = r.uri.replace('/models/', '').replace('/invoke', '')
attributes['tfs-model-name'] = model_name
} else {
attributes['tfs-model-name'] = r.variables.default_tfs_model
}
}
return attributes
}
function json_request(r) {
var data = r.requestText
if (is_tfs_json(data)) {
tfs_json_request(r, data)
} else if (is_json_lines(data)) {
json_lines_request(r, data)
} else {
generic_json_request(r, data)
}
}
function is_tfs_json(data) {
return /"(instances|inputs|examples)"\s*:/.test(data)
}
function is_json_lines(data) {
// objects separated only by (optional) whitespace means jsons/json-lines
return /[}\]]\s*[\[{]/.test(data)
}
function generic_json_request(r, data) {
if (! /^\s*\[\s*\[/.test(data)) {
data = '[' + data + ']'
}
var json = '{"instances":' + data + '}'
tfs_json_request(r, json)
}
function json_lines_request(r, data) {
var lines = data.trim().split(/\r?\n/)
var builder = []
builder.push('{"instances":')
if (lines.length != 1) {
builder.push('[')
}
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim()
if (line) {
var instance = (i == 0) ? '' : ','
instance += line
builder.push(instance)
}
}
builder.push(lines.length == 1 ? '}' : ']}')
tfs_json_request(r, builder.join(''))
}
function csv_request(r) {
var data = r.requestText
// look for initial quote or numeric-only data in 1st field
var needs_quotes = data.search(/^\s*("|[\d.Ee+\-]+.*)/) != 0
var lines = data.trim().split(/\r?\n/)
var builder = []
builder.push('{"instances":[')
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim()
if (line) {
var line_builder = []
// Only wrap line in brackets if there are multiple columns.
// If there's only one column and it has a string with a comma,
// the input will be wrapped in an extra set of brackets.
var has_multiple_columns = line.search(',') != -1
if (has_multiple_columns) {
line_builder.push('[')
}
if (needs_quotes) {
line_builder.push('"')
line_builder.push(line.replace('"', '\\"').replace(',', '","'))
line_builder.push('"')
} else {
line_builder.push(line)
}
if (has_multiple_columns) {
line_builder.push(']')
}
var json_line = line_builder.join('')
builder.push(json_line)
if (i != lines.length - 1)
builder.push(',')
}
}
builder.push(']}')
tfs_json_request(r, builder.join(''))
}
export default {invocations, ping, ping_without_model, return_error,
tfs_json_request, make_tfs_uri, parse_custom_attributes,
json_request, is_tfs_json, is_json_lines, generic_json_request,
json_lines_request, csv_request};