add more fine-grained request timeouts (#387)

* add more fine-grained request timeouts

fixes #381

* tweak timeout
This commit is contained in:
Nolan Lawson 2018-06-13 07:38:36 -07:00 committed by GitHub
parent a0d3a89180
commit 5f4e869eeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 92 additions and 114 deletions

View file

@ -1,17 +1,17 @@
import { getWithTimeout, postWithTimeout } from '../_utils/ajax'
import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from '../_api/utils'
export async function getFollowRequests (instanceName, accessToken) {
let url = `${basename(instanceName)}/api/v1/follow_requests`
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}
export async function authorizeFollowRequest (instanceName, accessToken, id) {
let url = `${basename(instanceName)}/api/v1/follow_requests/${id}/authorize`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}
export async function rejectFollowRequest (instanceName, accessToken, id) {
let url = `${basename(instanceName)}/api/v1/follow_requests/${id}/reject`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,12 +1,12 @@
import { auth, basename } from './utils'
import { postWithTimeout } from '../_utils/ajax'
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
export async function blockAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/block`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}
export async function unblockAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/unblock`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,14 +1,14 @@
import { getWithTimeout, paramsString } from '../_utils/ajax'
import { DEFAULT_TIMEOUT, get, paramsString } from '../_utils/ajax'
import { auth, basename } from './utils'
export async function getBlockedAccounts (instanceName, accessToken, limit = 80) {
let url = `${basename(instanceName)}/api/v1/blocks`
url += '?' + paramsString({ limit })
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}
export async function getMutedAccounts (instanceName, accessToken, limit = 80) {
let url = `${basename(instanceName)}/api/v1/mutes`
url += '?' + paramsString({ limit })
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,7 +1,7 @@
import { auth, basename } from './utils'
import { deleteWithTimeout } from '../_utils/ajax'
import { del, WRITE_TIMEOUT } from '../_utils/ajax'
export async function deleteStatus (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}`
return deleteWithTimeout(url, auth(accessToken))
return del(url, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,7 +1,7 @@
import { basename } from './utils'
import { getWithTimeout } from '../_utils/ajax'
import { DEFAULT_TIMEOUT, get } from '../_utils/ajax'
export async function getCustomEmoji (instanceName) {
let url = `${basename(instanceName)}/api/v1/custom_emojis`
return getWithTimeout(url)
return get(url, null, {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,12 +1,12 @@
import { post } from '../_utils/ajax'
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
import { basename, auth } from './utils'
export async function favoriteStatus (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/favourite`
return post(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}
export async function unfavoriteStatus (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/unfavourite`
return post(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,12 +1,12 @@
import { postWithTimeout } from '../_utils/ajax'
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export async function followAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/follow`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}
export async function unfollowAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/unfollow`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,14 +1,14 @@
import { getWithTimeout, paramsString } from '../_utils/ajax'
import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export async function getFollows (instanceName, accessToken, accountId, limit = 80) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/following`
url += '?' + paramsString({ limit })
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}
export async function getFollowers (instanceName, accessToken, accountId, limit = 80) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/followers`
url += '?' + paramsString({ limit })
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,7 +1,7 @@
import { getWithTimeout } from '../_utils/ajax'
import { get, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { basename } from './utils'
export function getInstanceInfo (instanceName) {
let url = `${basename(instanceName)}/api/v1/instance`
return getWithTimeout(url)
return get(url, null, {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,7 +1,7 @@
import { getWithTimeout } from '../_utils/ajax'
import { get, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export function getLists (instanceName, accessToken) {
let url = `${basename(instanceName)}/api/v1/lists`
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,5 +1,5 @@
import { auth, basename } from './utils'
import { postWithTimeout, putWithTimeout } from '../_utils/ajax'
import { post, put, MEDIA_WRITE_TIMEOUT, WRITE_TIMEOUT } from '../_utils/ajax'
export async function uploadMedia (instanceName, accessToken, file, description) {
let formData = new FormData()
@ -8,10 +8,10 @@ export async function uploadMedia (instanceName, accessToken, file, description)
formData.append('description', description)
}
let url = `${basename(instanceName)}/api/v1/media`
return postWithTimeout(url, formData, auth(accessToken))
return post(url, formData, auth(accessToken), {timeout: MEDIA_WRITE_TIMEOUT})
}
export async function putMediaDescription (instanceName, accessToken, mediaId, description) {
let url = `${basename(instanceName)}/api/v1/media/${mediaId}`
return putWithTimeout(url, {description}, auth(accessToken))
return put(url, {description}, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,12 +1,12 @@
import { auth, basename } from './utils'
import { postWithTimeout } from '../_utils/ajax'
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
export async function muteAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/mute`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}
export async function unmuteAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/unmute`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,12 +1,12 @@
import { auth, basename } from './utils'
import { postWithTimeout } from '../_utils/ajax'
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
export async function muteConversation (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/mute`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}
export async function unmuteConversation (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/unmute`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,4 +1,4 @@
import { postWithTimeout, paramsString } from '../_utils/ajax'
import { post, paramsString, WRITE_TIMEOUT } from '../_utils/ajax'
import { basename } from './utils'
const WEBSITE = 'https://pinafore.social'
@ -7,12 +7,12 @@ const CLIENT_NAME = 'Pinafore'
export function registerApplication (instanceName, redirectUri) {
const url = `${basename(instanceName)}/api/v1/apps`
return postWithTimeout(url, {
return post(url, {
client_name: CLIENT_NAME,
redirect_uris: redirectUri,
scopes: SCOPES,
website: WEBSITE
})
}, null, {timeout: WRITE_TIMEOUT})
}
export function generateAuthLink (instanceName, clientId, redirectUri) {
@ -27,11 +27,11 @@ export function generateAuthLink (instanceName, clientId, redirectUri) {
export function getAccessTokenFromAuthCode (instanceName, clientId, clientSecret, code, redirectUri) {
let url = `${basename(instanceName)}/oauth/token`
return postWithTimeout(url, {
return post(url, {
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
code: code
})
}, null, {timeout: WRITE_TIMEOUT})
}

View file

@ -1,12 +1,12 @@
import { postWithTimeout } from '../_utils/ajax'
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export async function pinStatus (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/pin`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}
export async function unpinStatus (instanceName, accessToken, statusId) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/unpin`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,4 +1,4 @@
import { getWithTimeout, paramsString } from '../_utils/ajax'
import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export async function getPinnedStatuses (instanceName, accessToken, accountId) {
@ -7,5 +7,5 @@ export async function getPinnedStatuses (instanceName, accessToken, accountId) {
limit: 40,
pinned: true
})
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,14 +1,14 @@
import { getWithTimeout, paramsString } from '../_utils/ajax'
import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export async function getReblogs (instanceName, accessToken, statusId, limit = 80) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/reblogged_by`
url += '?' + paramsString({ limit })
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}
export async function getFavorites (instanceName, accessToken, statusId, limit = 80) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/favourited_by`
url += '?' + paramsString({ limit })
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,12 +1,12 @@
import { postWithTimeout } from '../_utils/ajax'
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export async function approveFollowRequest (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/follow_requests/${accountId}/authorize`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}
export async function rejectFollowRequest (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/follow_requests/${accountId}/reject`
return postWithTimeout(url, null, auth(accessToken))
return post(url, null, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,4 +1,4 @@
import { getWithTimeout, paramsString } from '../_utils/ajax'
import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export function search (instanceName, accessToken, query) {
@ -6,5 +6,5 @@ export function search (instanceName, accessToken, query) {
q: query,
resolve: true
})
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,5 +1,5 @@
import { auth, basename } from './utils'
import { postWithTimeout } from '../_utils/ajax'
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility) {
@ -21,5 +21,5 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId,
}
}
return postWithTimeout(url, body, auth(accessToken))
return post(url, body, auth(accessToken), {timeout: WRITE_TIMEOUT})
}

View file

@ -1,4 +1,4 @@
import { getWithTimeout, paramsString } from '../_utils/ajax'
import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
function getTimelineUrlPath (timeline) {
@ -57,12 +57,12 @@ export function getTimeline (instanceName, accessToken, timeline, maxId, since)
// special case - this is a list of descendents and ancestors
let statusUrl = `${basename(instanceName)}/api/v1/statuses/${timeline.split('/').slice(-1)[0]}`
return Promise.all([
getWithTimeout(url, auth(accessToken)),
getWithTimeout(statusUrl, auth(accessToken))
get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT}),
get(statusUrl, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
]).then(res => {
return [].concat(res[0].ancestors).concat([res[1]]).concat(res[0].descendants)
})
}
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}

View file

@ -1,19 +1,19 @@
import { getWithTimeout, paramsString } from '../_utils/ajax'
import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils'
export function getVerifyCredentials (instanceName, accessToken) {
let url = `${basename(instanceName)}/api/v1/accounts/verify_credentials`
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}
export function getAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}`
return getWithTimeout(url, auth(accessToken))
return get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
}
export async function getRelationship (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/relationships`
url += '?' + paramsString({id: accountId})
let res = await getWithTimeout(url, auth(accessToken))
let res = await get(url, auth(accessToken), {timeout: DEFAULT_TIMEOUT})
return res[0]
}

View file

@ -1,13 +1,15 @@
const TIMEOUT = process.browser ? 60000 : 120000
export const DEFAULT_TIMEOUT = 20000
export const MEDIA_WRITE_TIMEOUT = 90000 // media uploads can take awhile
export const WRITE_TIMEOUT = 45000 // allow more time if the user did a write action
function fetchWithTimeout (url, options) {
function fetchWithTimeout (url, fetchOptions, timeout) {
return new Promise((resolve, reject) => {
fetch(url, options).then(resolve, reject)
setTimeout(() => reject(new Error(`Timed out after ${TIMEOUT / 1000} seconds`)), TIMEOUT)
fetch(url, fetchOptions).then(resolve, reject)
setTimeout(() => reject(new Error(`Timed out after ${timeout / 1000} seconds`)), timeout)
})
}
function makeOpts (method, headers) {
function makeFetchOptions (method, headers) {
return {
method,
headers: Object.assign(headers || {}, {
@ -27,67 +29,43 @@ async function throwErrorIfInvalidResponse (response) {
throw new Error('Request failed: ' + response.status)
}
async function _fetch (url, options, timeout) {
let fetchFunc = timeout ? fetchWithTimeout : fetch
let response = await fetchFunc(url, options)
async function _fetch (url, fetchOptions, options) {
let response
if (options && options.timeout) {
response = await fetchWithTimeout(url, fetchOptions, options.timeout)
} else {
response = await fetch(url, fetchOptions)
}
return throwErrorIfInvalidResponse(response)
}
async function _putOrPost (url, body, headers, timeout, method) {
let opts = makeOpts(method, headers)
async function _putOrPost (method, url, body, headers, options) {
let fetchOptions = makeFetchOptions(method, headers)
if (body) {
if (body instanceof FormData) {
opts.body = body
fetchOptions.body = body
} else {
opts.body = JSON.stringify(body)
opts.headers['Content-Type'] = 'application/json'
fetchOptions.body = JSON.stringify(body)
fetchOptions.headers['Content-Type'] = 'application/json'
}
}
return _fetch(url, opts, timeout)
return _fetch(url, fetchOptions, options)
}
async function _post (url, body, headers, timeout) {
return _putOrPost(url, body, headers, timeout, 'POST')
export async function put (url, body, headers, options) {
return _putOrPost('PUT', url, body, headers, options)
}
async function _put (url, body, headers, timeout) {
return _putOrPost(url, body, headers, timeout, 'PUT')
export async function post (url, body, headers, options) {
return _putOrPost('POST', url, body, headers, options)
}
async function _get (url, headers, timeout) {
return _fetch(url, makeOpts('GET', headers), timeout)
export async function get (url, headers, options) {
return _fetch(url, makeFetchOptions('GET', headers), options)
}
async function _delete (url, headers, timeout) {
return _fetch(url, makeOpts('DELETE', headers), timeout)
}
export async function put (url, body, headers) {
return _put(url, body, headers, false)
}
export async function putWithTimeout (url, body, headers) {
return _put(url, body, headers, true)
}
export async function post (url, body, headers) {
return _post(url, body, headers, false)
}
export async function postWithTimeout (url, body, headers) {
return _post(url, body, headers, true)
}
export async function getWithTimeout (url, headers) {
return _get(url, headers, true)
}
export async function get (url, headers) {
return _get(url, headers, false)
}
export async function deleteWithTimeout (url, headers) {
return _delete(url, headers, true)
export async function del (url, headers, options) {
return _fetch(url, makeFetchOptions('DELETE', headers), options)
}
export function paramsString (paramsObject) {