api.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { Configuration, VersionResponse } from './generated'
  2. import { FilesApi, StateApi } from './generated/api'
  3. import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
  4. import { LoggingService } from '../../logging'
  5. import { Logger } from 'winston'
  6. import { ReadonlyConfig, StorageNodeDownloadResponse } from '../../../types'
  7. import { parseAxiosError } from '../../parsers/errors'
  8. export class StorageNodeApi {
  9. private logger: Logger
  10. private filesApi: FilesApi
  11. private stateApi: StateApi
  12. private config: ReadonlyConfig
  13. public endpoint: string
  14. public constructor(endpoint: string, logging: LoggingService, config: ReadonlyConfig) {
  15. this.config = config
  16. const apiConfig = new Configuration({
  17. basePath: endpoint,
  18. })
  19. this.filesApi = new FilesApi(apiConfig)
  20. this.stateApi = new StateApi(apiConfig)
  21. this.endpoint = new URL(endpoint).toString()
  22. this.logger = logging.createLogger('StorageNodeApi', { endpoint })
  23. }
  24. // Adds timeout for the request which can additionaly take into account response processing time.
  25. private reqConfigWithTimeout(options: AxiosRequestConfig, timeoutMs: number): [AxiosRequestConfig, NodeJS.Timeout] {
  26. const source = axios.CancelToken.source()
  27. const timeout = setTimeout(() => {
  28. this.logger.error(`Request timeout of ${timeoutMs}ms reached`, { timeoutMs })
  29. source.cancel('Request timeout')
  30. }, timeoutMs)
  31. return [
  32. {
  33. ...options,
  34. cancelToken: source.token,
  35. },
  36. timeout,
  37. ]
  38. }
  39. public async isObjectAvailable(objectId: string): Promise<boolean> {
  40. const [options, timeout] = this.reqConfigWithTimeout({}, this.config.limits.outboundRequestsTimeoutMs)
  41. this.logger.debug('Checking object availibility', { objectId })
  42. try {
  43. await this.filesApi.filesApiGetFileHeaders(objectId, options)
  44. this.logger.debug('Data object available', { objectId })
  45. return true
  46. } catch (err) {
  47. if (axios.isAxiosError(err)) {
  48. this.logger.debug('Data object not available', { objectId, err: parseAxiosError(err) })
  49. return false
  50. }
  51. this.logger.error('Unexpected error while requesting data object', { objectId, err })
  52. throw err
  53. } finally {
  54. clearTimeout(timeout)
  55. }
  56. }
  57. public async downloadObject(objectId: string, startAt?: number): Promise<StorageNodeDownloadResponse> {
  58. this.logger.verbose('Sending download request', { objectId, startAt })
  59. const [options, timeout] = this.reqConfigWithTimeout(
  60. {
  61. responseType: 'stream',
  62. },
  63. this.config.limits.pendingDownloadTimeoutSec * 1000
  64. )
  65. if (startAt) {
  66. options.headers.Range = `bytes=${startAt}-`
  67. }
  68. try {
  69. const response: StorageNodeDownloadResponse = await this.filesApi.filesApiGetFile(objectId, options)
  70. response.data.on('end', () => clearTimeout(timeout))
  71. response.data.on('error', () => clearTimeout(timeout))
  72. return response
  73. } catch (err) {
  74. clearTimeout(timeout)
  75. throw err
  76. }
  77. }
  78. public async getVersion(): Promise<AxiosResponse<VersionResponse>> {
  79. const [options, timeout] = this.reqConfigWithTimeout(
  80. {
  81. headers: {
  82. connection: 'close',
  83. },
  84. },
  85. this.config.limits.outboundRequestsTimeoutMs
  86. )
  87. try {
  88. const response = await this.stateApi.stateApiGetVersion(options)
  89. return response
  90. } finally {
  91. clearTimeout(timeout)
  92. }
  93. }
  94. }