license /**
 * IOHandler implementations based on HTTP requests in the web browser.
 *
 * Uses [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
 */ import { env } from '../environment' ; import { assert } from '../util' ; import { concatenateArrayBuffers , getModelArtifactsForJSON , getModelArtifactsInfoForJSON , getModelJSONForModelArtifacts } from './io_utils' ; import { IORouter , IORouterRegistry } from './router_registry' ; import { IOHandler , LoadOptions , ModelArtifacts , ModelJSON , OnProgressCallback , SaveResult , WeightsManifestConfig , WeightsManifestEntry } from './types' ; import { loadWeightsAsArrayBuffer } from './weights_loader' ; const OCTET_STREAM_MIME_TYPE = 'application/octet-stream' ; const JSON_TYPE = 'application/json' ; export class HTTPRequest implements IOHandler { protected readonly path : string ; protected readonly requestInit : RequestInit ; private readonly fetch : Function ; private readonly weightUrlConverter : ( weightName : string ) => Promise < string > ; readonly DEFAULT_METHOD = 'POST' ; static readonly URL_SCHEME_REGEX = /^https?:\/\// ; private readonly weightPathPrefix : string ; private readonly onProgress : OnProgressCallback ; constructor ( path , loadOptions ? ) { if ( loadOptions == null ) { loadOptions = { } ; } this . weightPathPrefix = loadOptions . weightPathPrefix ; this . onProgress = loadOptions . onProgress ; this . weightUrlConverter = loadOptions . weightUrlConverter ; if ( loadOptions . fetchFunc != null ) { assert ( typeof loadOptions . fetchFunc === 'function' , ( ) => 'Must pass a function that matches the signature of ' + '`fetch` (see ' + 'https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)' ) ; this . fetch = loadOptions . fetchFunc ; } else { this . fetch = env ( ) . platform . fetch ; } assert ( path != null && path . length > 0 , ( ) => 'URL path for http must not be null, undefined or ' + 'empty.' ) ; if ( Array . isArray ( path ) ) { assert ( path . length === 2 , ( ) => 'URL paths for http must have a length of 2, ' + `(actual length is ${ path . length }).` ) ; } this . path = path ; if ( loadOptions . requestInit != null && loadOptions . requestInit . body != null ) { throw new Error ( 'requestInit is expected to have no pre-existing body, but has one.' ) ; } this . requestInit = loadOptions . requestInit || { } ; } async save ( modelArtifacts ) { if ( modelArtifacts . modelTopology instanceof ArrayBuffer ) { throw new Error ( 'BrowserHTTPRequest.save() does not support saving model topology ' + 'in binary formats yet.' ) ; } const init = Object . assign ( { method : this . DEFAULT_METHOD } , this . requestInit ) ; init . body = new FormData ( ) ; const weightsManifest = [ { paths : [ './model.weights.bin' ] , weights : modelArtifacts . weightSpecs , } ] ; const modelTopologyAndWeightManifest = getModelJSONForModelArtifacts ( modelArtifacts , weightsManifest ) ; init . body . append ( 'model.json' , new Blob ( [ JSON . stringify ( modelTopologyAndWeightManifest ) ] , { type : JSON_TYPE } ) , 'model.json' ) ; if ( modelArtifacts . weightData != null ) { init . body . append ( 'model.weights.bin' , new Blob ( [ modelArtifacts . weightData ] , { type : OCTET_STREAM_MIME_TYPE } ) , 'model.weights.bin' ) ; } const response = await this . fetch ( this . path , init ) ; if ( response . ok ) { return { modelArtifactsInfo : getModelArtifactsInfoForJSON ( modelArtifacts ) , responses : [ response ] , } ; } else { throw new Error ( `BrowserHTTPRequest.save() failed due to HTTP response status ` + `${ response . status }.` ) ; } } returns async load ( ) { const modelConfigRequest = await this . fetch ( this . path , this . requestInit ) ; if ( ! modelConfigRequest . ok ) { throw new Error ( `Request to ${ this . path } failed with status code ` + `${ modelConfigRequest . status }. Please verify this URL points to ` + `the model JSON of the model to load.` ) ; } let modelJSON ; try { modelJSON = await modelConfigRequest . json ( ) ; } catch ( e ) { let message = `Failed to parse model JSON of response from ${ this . path }.` ; if ( this . path . endsWith ( '.pb' ) ) { message += ' Your path contains a .pb file extension. ' + 'Support for .pb models have been removed in TensorFlow.js 1.0 ' + 'in favor of .json models. You can re-convert your Python ' + 'TensorFlow model using the TensorFlow.js 1.0 conversion scripts ' + 'or you can convert your.pb models with the \'pb2json\'' + 'NPM script in the tensorflow/tfjs-converter repository.' ; } else { message += ' Please make sure the server is serving valid ' + 'JSON for this request.' ; } throw new Error ( message ) ; } const modelTopology = modelJSON . modelTopology ; const weightsManifest = modelJSON . weightsManifest ; if ( modelTopology == null && weightsManifest == null ) { throw new Error ( `The JSON from HTTP path ${ this . path } contains neither model ` + `topology or manifest for weights.` ) ; } return getModelArtifactsForJSON ( modelJSON , ( weightsManifest ) => this . loadWeights ( weightsManifest ) ) ; } private async loadWeights ( weightsManifest ) { const weightPath = Array . isArray ( this . path ) ? this . path [ 1 ] : this . path ; const [ prefix , suffix ] = parseUrl ( weightPath ) ; const pathPrefix = this . weightPathPrefix || prefix ; const weightSpecs = [ ] ; for ( const entry of weightsManifest ) { weightSpecs . push ( ... entry . weights ) ; } const fetchURLs = [ ] ; const urlPromises = [ ] ; for ( const weightsGroup of weightsManifest ) { for ( const path of weightsGroup . paths ) { if ( this . weightUrlConverter != null ) { urlPromises . push ( this . weightUrlConverter ( path ) ) ; } else { fetchURLs . push ( pathPrefix + path + suffix ) ; } } } if ( this . weightUrlConverter ) { fetchURLs . push ( ... await Promise . all ( urlPromises ) ) ; } const buffers = await loadWeightsAsArrayBuffer ( fetchURLs , { requestInit : this . requestInit , fetchFunc : this . fetch , onProgress : this . onProgress } ) ; return [ weightSpecs , concatenateArrayBuffers ( buffers ) ] ; } } param url export function parseUrl ( url ) { const lastSlash = url . lastIndexOf ( '/' ) ; const lastSearchParam = url . lastIndexOf ( '?' ) ; const prefix = url . substring ( 0 , lastSlash ) ; const suffix = lastSearchParam > lastSlash ? url . substring ( lastSearchParam ) : '' ; return [ prefix + '/' , suffix ] ; } export function isHTTPScheme ( url ) { return url . match ( HTTPRequest . URL_SCHEME_REGEX ) != null ; } export const httpRouter = ( url , loadOptions ? ) => { if ( typeof fetch === 'undefined' && ( loadOptions == null || loadOptions . fetchFunc == null ) ) { return null ; } else { let isHTTP = true ; if ( Array . isArray ( url ) ) { isHTTP = url . every ( urlItem => isHTTPScheme ( urlItem ) ) ; } else { isHTTP = isHTTPScheme ( url ) ; } if ( isHTTP ) { return http ( url , loadOptions ) ; } } return null ; } ; IORouterRegistry . registerSaveRouter ( httpRouter ) ; IORouterRegistry . registerLoadRouter ( httpRouter ) ; param path param requestInit param loadOptions returns doc export function http ( path , loadOptions ? ) { return new HTTPRequest ( path , loadOptions ) ; } param path param loadOptions export function browserHTTPRequest ( path , loadOptions ? ) { return http ( path , loadOptions ) ; }