@neteasecloudmusicapienhanced/api 4.29.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.MD +178 -0
- package/app.js +18 -0
- package/data/deviceid.txt +24641 -0
- package/generateConfig.js +24 -0
- package/interface.d.ts +1834 -0
- package/main.js +63 -0
- package/module/activate_init_profile.js +9 -0
- package/module/aidj_content_rcmd.js +28 -0
- package/module/album.js +6 -0
- package/module/album_detail.js +12 -0
- package/module/album_detail_dynamic.js +12 -0
- package/module/album_list.js +16 -0
- package/module/album_list_style.js +15 -0
- package/module/album_new.js +11 -0
- package/module/album_newest.js +6 -0
- package/module/album_privilege.js +9 -0
- package/module/album_songsaleboard.js +19 -0
- package/module/album_sub.js +10 -0
- package/module/album_sublist.js +11 -0
- package/module/api.js +21 -0
- package/module/artist_album.js +15 -0
- package/module/artist_desc.js +9 -0
- package/module/artist_detail.js +10 -0
- package/module/artist_detail_dynamic.js +9 -0
- package/module/artist_fans.js +11 -0
- package/module/artist_follow_count.js +13 -0
- package/module/artist_list.js +33 -0
- package/module/artist_mv.js +12 -0
- package/module/artist_new_mv.js +12 -0
- package/module/artist_new_song.js +12 -0
- package/module/artist_songs.js +12 -0
- package/module/artist_sub.js +11 -0
- package/module/artist_sublist.js +11 -0
- package/module/artist_top_song.js +8 -0
- package/module/artist_video.js +15 -0
- package/module/artists.js +6 -0
- package/module/audio_match.js +19 -0
- package/module/avatar_upload.js +22 -0
- package/module/banner.js +16 -0
- package/module/batch.js +12 -0
- package/module/broadcast_category_region_get.js +11 -0
- package/module/broadcast_channel_collect_list.js +12 -0
- package/module/broadcast_channel_currentinfo.js +13 -0
- package/module/broadcast_channel_list.js +13 -0
- package/module/broadcast_sub.js +12 -0
- package/module/calendar.js +8 -0
- package/module/captcha_sent.js +11 -0
- package/module/captcha_verify.js +11 -0
- package/module/cellphone_existence_check.js +10 -0
- package/module/check_music.js +30 -0
- package/module/cloud.js +146 -0
- package/module/cloud_import.js +39 -0
- package/module/cloud_match.js +13 -0
- package/module/cloudsearch.js +13 -0
- package/module/comment.js +30 -0
- package/module/comment_album.js +16 -0
- package/module/comment_dj.js +16 -0
- package/module/comment_event.js +15 -0
- package/module/comment_floor.js +16 -0
- package/module/comment_hot.js +18 -0
- package/module/comment_hug_list.js +20 -0
- package/module/comment_like.js +20 -0
- package/module/comment_music.js +16 -0
- package/module/comment_mv.js +16 -0
- package/module/comment_new.js +37 -0
- package/module/comment_playlist.js +16 -0
- package/module/comment_video.js +16 -0
- package/module/countries_code_list.js +6 -0
- package/module/creator_authinfo_get.js +6 -0
- package/module/daily_signin.js +16 -0
- package/module/digitalAlbum_detail.js +13 -0
- package/module/digitalAlbum_ordering.js +22 -0
- package/module/digitalAlbum_purchased.js +15 -0
- package/module/digitalAlbum_sales.js +13 -0
- package/module/djRadio_top.js +15 -0
- package/module/dj_banner.js +6 -0
- package/module/dj_category_excludehot.js +10 -0
- package/module/dj_category_recommend.js +10 -0
- package/module/dj_catelist.js +6 -0
- package/module/dj_detail.js +9 -0
- package/module/dj_hot.js +10 -0
- package/module/dj_paygift.js +15 -0
- package/module/dj_personalize_recommend.js +12 -0
- package/module/dj_program.js +12 -0
- package/module/dj_program_detail.js +9 -0
- package/module/dj_program_toplist.js +10 -0
- package/module/dj_program_toplist_hours.js +13 -0
- package/module/dj_radio_hot.js +11 -0
- package/module/dj_recommend.js +6 -0
- package/module/dj_recommend_type.js +32 -0
- package/module/dj_sub.js +10 -0
- package/module/dj_sublist.js +11 -0
- package/module/dj_subscriber.js +12 -0
- package/module/dj_today_perfered.js +13 -0
- package/module/dj_toplist.js +14 -0
- package/module/dj_toplist_hours.js +10 -0
- package/module/dj_toplist_newcomer.js +9 -0
- package/module/dj_toplist_pay.js +9 -0
- package/module/dj_toplist_popular.js +10 -0
- package/module/eapi_decrypt.js +27 -0
- package/module/event.js +10 -0
- package/module/event_del.js +9 -0
- package/module/event_forward.js +11 -0
- package/module/fanscenter_basicinfo_age_get.js +6 -0
- package/module/fanscenter_basicinfo_gender_get.js +10 -0
- package/module/fanscenter_basicinfo_province_get.js +10 -0
- package/module/fanscenter_overview_get.js +6 -0
- package/module/fanscenter_trend_list.js +10 -0
- package/module/fm_trash.js +11 -0
- package/module/follow.js +11 -0
- package/module/get_userids.js +7 -0
- package/module/history_recommend_songs.js +11 -0
- package/module/history_recommend_songs_detail.js +13 -0
- package/module/homepage_block_page.js +8 -0
- package/module/homepage_dragon_ball.js +10 -0
- package/module/hot_topic.js +10 -0
- package/module/hug_comment.js +16 -0
- package/module/inner_version.js +16 -0
- package/module/like.js +13 -0
- package/module/likelist.js +9 -0
- package/module/listen_data_realtime_report.js +11 -0
- package/module/listen_data_report.js +12 -0
- package/module/listen_data_today_song.js +9 -0
- package/module/listen_data_total.js +9 -0
- package/module/listen_data_year_report.js +9 -0
- package/module/listentogether_accept.js +13 -0
- package/module/listentogether_end.js +9 -0
- package/module/listentogether_heatbeat.js +12 -0
- package/module/listentogether_play_command.js +21 -0
- package/module/listentogether_room_check.js +9 -0
- package/module/listentogether_room_create.js +9 -0
- package/module/listentogether_status.js +10 -0
- package/module/listentogether_sync_list_command.js +26 -0
- package/module/listentogether_sync_playlist_get.js +13 -0
- package/module/login.js +41 -0
- package/module/login_cellphone.js +40 -0
- package/module/login_qr_check.js +29 -0
- package/module/login_qr_create.js +30 -0
- package/module/login_qr_key.js +19 -0
- package/module/login_refresh.js +21 -0
- package/module/login_status.js +21 -0
- package/module/logout.js +6 -0
- package/module/lyric.js +14 -0
- package/module/lyric_new.js +17 -0
- package/module/mlog_music_rcmd.js +13 -0
- package/module/mlog_to_video.js +13 -0
- package/module/mlog_url.js +11 -0
- package/module/msg_comments.js +17 -0
- package/module/msg_forwards.js +11 -0
- package/module/msg_notices.js +10 -0
- package/module/msg_private.js +11 -0
- package/module/msg_private_history.js +12 -0
- package/module/msg_recentcontact.js +11 -0
- package/module/music_first_listen_info.js +13 -0
- package/module/musician_cloudbean.js +7 -0
- package/module/musician_cloudbean_obtain.js +14 -0
- package/module/musician_data_overview.js +11 -0
- package/module/musician_play_trend.js +14 -0
- package/module/musician_sign.js +7 -0
- package/module/musician_tasks.js +11 -0
- package/module/musician_tasks_new.js +11 -0
- package/module/mv_all.js +16 -0
- package/module/mv_detail.js +9 -0
- package/module/mv_detail_info.js +14 -0
- package/module/mv_exclusive_rcmd.js +10 -0
- package/module/mv_first.js +12 -0
- package/module/mv_sub.js +11 -0
- package/module/mv_sublist.js +15 -0
- package/module/mv_url.js +14 -0
- package/module/nickname_check.js +7 -0
- package/module/personal_fm.js +6 -0
- package/module/personal_fm_mode.js +14 -0
- package/module/personalized.js +16 -0
- package/module/personalized_djprogram.js +10 -0
- package/module/personalized_mv.js +6 -0
- package/module/personalized_newsong.js +15 -0
- package/module/personalized_privatecontent.js +10 -0
- package/module/personalized_privatecontent_list.js +15 -0
- package/module/pl_count.js +6 -0
- package/module/playlist_category_list.js +11 -0
- package/module/playlist_catlist.js +6 -0
- package/module/playlist_cover_update.js +32 -0
- package/module/playlist_create.js +11 -0
- package/module/playlist_delete.js +9 -0
- package/module/playlist_desc_update.js +10 -0
- package/module/playlist_detail.js +11 -0
- package/module/playlist_detail_dynamic.js +11 -0
- package/module/playlist_detail_rcmd_get.js +11 -0
- package/module/playlist_highquality_tags.js +10 -0
- package/module/playlist_hot.js +6 -0
- package/module/playlist_import_name_task_create.js +62 -0
- package/module/playlist_import_task_status.js +11 -0
- package/module/playlist_mylike.js +12 -0
- package/module/playlist_name_update.js +10 -0
- package/module/playlist_order_update.js +13 -0
- package/module/playlist_privacy.js +10 -0
- package/module/playlist_subscribe.js +14 -0
- package/module/playlist_subscribers.js +11 -0
- package/module/playlist_tags_update.js +10 -0
- package/module/playlist_track_add.js +16 -0
- package/module/playlist_track_all.js +31 -0
- package/module/playlist_track_delete.js +20 -0
- package/module/playlist_tracks.js +45 -0
- package/module/playlist_update.js +13 -0
- package/module/playlist_update_playcount.js +9 -0
- package/module/playlist_video_recent.js +9 -0
- package/module/playmode_intelligence_list.js +13 -0
- package/module/playmode_song_vector.js +8 -0
- package/module/program_recommend.js +15 -0
- package/module/rebind.js +16 -0
- package/module/recent_listen_list.js +7 -0
- package/module/recommend_resource.js +10 -0
- package/module/recommend_songs.js +11 -0
- package/module/recommend_songs_dislike.js +14 -0
- package/module/record_recent_album.js +11 -0
- package/module/record_recent_dj.js +11 -0
- package/module/record_recent_playlist.js +11 -0
- package/module/record_recent_song.js +11 -0
- package/module/record_recent_video.js +11 -0
- package/module/record_recent_voice.js +11 -0
- package/module/register_anonimous.js +52 -0
- package/module/register_cellphone.js +15 -0
- package/module/related_allvideo.js +14 -0
- package/module/related_playlist.js +32 -0
- package/module/resource_like.js +14 -0
- package/module/scrobble.js +26 -0
- package/module/search.js +21 -0
- package/module/search_default.js +6 -0
- package/module/search_hot.js +9 -0
- package/module/search_hot_detail.js +6 -0
- package/module/search_match.js +18 -0
- package/module/search_multimatch.js +14 -0
- package/module/search_suggest.js +14 -0
- package/module/send_album.js +12 -0
- package/module/send_playlist.js +12 -0
- package/module/send_song.js +12 -0
- package/module/send_text.js +11 -0
- package/module/setting.js +7 -0
- package/module/share_resource.js +11 -0
- package/module/sheet_list.js +9 -0
- package/module/sheet_preview.js +8 -0
- package/module/sign_happy_info.js +5 -0
- package/module/signin_progress.js +13 -0
- package/module/simi_artist.js +12 -0
- package/module/simi_mv.js +9 -0
- package/module/simi_playlist.js +15 -0
- package/module/simi_song.js +15 -0
- package/module/simi_user.js +11 -0
- package/module/song_chorus.js +11 -0
- package/module/song_detail.js +11 -0
- package/module/song_downlist.js +11 -0
- package/module/song_download_url.js +10 -0
- package/module/song_download_url_v1.js +13 -0
- package/module/song_dynamic_cover.js +9 -0
- package/module/song_like_check.js +9 -0
- package/module/song_lyrics_mark.js +9 -0
- package/module/song_lyrics_mark_add.js +12 -0
- package/module/song_lyrics_mark_del.js +9 -0
- package/module/song_lyrics_mark_user_page.js +14 -0
- package/module/song_monthdownlist.js +11 -0
- package/module/song_music_detail.js +9 -0
- package/module/song_order_update.js +12 -0
- package/module/song_purchased.js +14 -0
- package/module/song_red_count.js +9 -0
- package/module/song_singledownlist.js +11 -0
- package/module/song_url.js +26 -0
- package/module/song_url_match.js +35 -0
- package/module/song_url_ncmget.js +75 -0
- package/module/song_url_unblock.js +35 -0
- package/module/song_url_v1.js +54 -0
- package/module/song_wiki_summary.js +8 -0
- package/module/starpick_comments_summary.js +12 -0
- package/module/style_album.js +16 -0
- package/module/style_artist.js +16 -0
- package/module/style_detail.js +9 -0
- package/module/style_list.js +7 -0
- package/module/style_playlist.js +16 -0
- package/module/style_preference.js +11 -0
- package/module/style_song.js +12 -0
- package/module/summary_annual.js +12 -0
- package/module/threshold_detail_get.js +10 -0
- package/module/top_album.js +22 -0
- package/module/top_artists.js +11 -0
- package/module/top_list.js +20 -0
- package/module/top_mv.js +12 -0
- package/module/top_playlist.js +22 -0
- package/module/top_playlist_highquality.js +16 -0
- package/module/top_song.js +16 -0
- package/module/topic_detail.js +7 -0
- package/module/topic_detail_event_hot.js +7 -0
- package/module/topic_sublist.js +11 -0
- package/module/toplist.js +6 -0
- package/module/toplist_artist.js +12 -0
- package/module/toplist_detail.js +6 -0
- package/module/toplist_detail_v2.js +6 -0
- package/module/ugc_album_get.js +8 -0
- package/module/ugc_artist_get.js +8 -0
- package/module/ugc_artist_search.js +10 -0
- package/module/ugc_detail.js +17 -0
- package/module/ugc_mv_get.js +8 -0
- package/module/ugc_song_get.js +8 -0
- package/module/ugc_user_devote.js +6 -0
- package/module/user_account.js +5 -0
- package/module/user_audio.js +9 -0
- package/module/user_binding.js +9 -0
- package/module/user_bindingcellphone.js +15 -0
- package/module/user_cloud.js +10 -0
- package/module/user_cloud_del.js +9 -0
- package/module/user_cloud_detail.js +10 -0
- package/module/user_comment_history.js +15 -0
- package/module/user_detail.js +15 -0
- package/module/user_detail_new.js +20 -0
- package/module/user_dj.js +14 -0
- package/module/user_event.js +12 -0
- package/module/user_follow_mixed.js +23 -0
- package/module/user_followeds.js +17 -0
- package/module/user_follows.js +15 -0
- package/module/user_level.js +7 -0
- package/module/user_medal.js +11 -0
- package/module/user_mutualfollow_get.js +9 -0
- package/module/user_playlist.js +12 -0
- package/module/user_record.js +10 -0
- package/module/user_replacephone.js +14 -0
- package/module/user_social_status.js +11 -0
- package/module/user_social_status_edit.js +16 -0
- package/module/user_social_status_rcmd.js +5 -0
- package/module/user_social_status_support.js +5 -0
- package/module/user_subcount.js +6 -0
- package/module/user_update.js +15 -0
- package/module/verify_getQr.js +39 -0
- package/module/verify_qrcodestatus.js +12 -0
- package/module/video_category_list.js +15 -0
- package/module/video_detail.js +13 -0
- package/module/video_detail_info.js +14 -0
- package/module/video_group.js +16 -0
- package/module/video_group_list.js +11 -0
- package/module/video_sub.js +14 -0
- package/module/video_timeline_all.js +17 -0
- package/module/video_timeline_recommend.js +13 -0
- package/module/video_url.js +10 -0
- package/module/vip_growthpoint.js +11 -0
- package/module/vip_growthpoint_details.js +14 -0
- package/module/vip_growthpoint_get.js +13 -0
- package/module/vip_info.js +12 -0
- package/module/vip_info_v2.js +12 -0
- package/module/vip_tasks.js +11 -0
- package/module/vip_timemachine.js +17 -0
- package/module/voice_delete.js +7 -0
- package/module/voice_detail.js +7 -0
- package/module/voice_lyric.js +7 -0
- package/module/voice_upload.js +186 -0
- package/module/voicelist_detail.js +11 -0
- package/module/voicelist_list.js +13 -0
- package/module/voicelist_list_search.js +14 -0
- package/module/voicelist_search.js +14 -0
- package/module/voicelist_trans.js +15 -0
- package/module/weblog.js +10 -0
- package/module/yunbei.js +6 -0
- package/module/yunbei_expense.js +8 -0
- package/module/yunbei_info.js +5 -0
- package/module/yunbei_rcmd_song.js +17 -0
- package/module/yunbei_rcmd_song_history.js +16 -0
- package/module/yunbei_receipt.js +8 -0
- package/module/yunbei_sign.js +5 -0
- package/module/yunbei_task_finish.js +12 -0
- package/module/yunbei_tasks.js +9 -0
- package/module/yunbei_tasks_todo.js +9 -0
- package/module/yunbei_today.js +5 -0
- package/package.json +104 -0
- package/plugins/songUpload.js +61 -0
- package/plugins/upload.js +37 -0
- package/public/api.html +127 -0
- package/public/audio_match_demo/afp.js +1627 -0
- package/public/audio_match_demo/afp.wasm.js +5 -0
- package/public/audio_match_demo/index.html +199 -0
- package/public/audio_match_demo/rec.js +49 -0
- package/public/avatar_update.html +73 -0
- package/public/cloud.html +104 -0
- package/public/docs/.nojekyll +0 -0
- package/public/docs/_coverpage.md +13 -0
- package/public/docs/favicon.ico +0 -0
- package/public/docs/home.md +4857 -0
- package/public/docs/icon.png +0 -0
- package/public/docs/index.html +47 -0
- package/public/docs/ncmapireborn.png +0 -0
- package/public/docs/sw.js +90 -0
- package/public/eapi_decrypt.html +77 -0
- package/public/home.html +41 -0
- package/public/index.html +313 -0
- package/public/listen_together_host.html +324 -0
- package/public/login.html +47 -0
- package/public/playlist_cover_update.html +77 -0
- package/public/playlist_import.html +262 -0
- package/public/qrlogin-nocookie.html +72 -0
- package/public/qrlogin.html +71 -0
- package/public/static/docs.png +0 -0
- package/public/static/eapi_params.png +0 -0
- package/public/static/eapi_response.png +0 -0
- package/public/static/screenshot1.png +0 -0
- package/public/unblock_test.html +123 -0
- package/public/voice_upload.html +128 -0
- package/server.js +366 -0
- package/util/apicache.js +833 -0
- package/util/client-sign.js +169 -0
- package/util/config.json +20 -0
- package/util/crypto.js +135 -0
- package/util/index.js +159 -0
- package/util/logger.js +29 -0
- package/util/memory-cache.js +71 -0
- package/util/option.js +14 -0
- package/util/request.js +364 -0
package/util/apicache.js
ADDED
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
var url = require('url')
|
|
2
|
+
var MemoryCache = require('./memory-cache')
|
|
3
|
+
const logger = require('./logger.js')
|
|
4
|
+
|
|
5
|
+
var t = {
|
|
6
|
+
ms: 1,
|
|
7
|
+
second: 1000,
|
|
8
|
+
minute: 60000,
|
|
9
|
+
hour: 3600000,
|
|
10
|
+
day: 3600000 * 24,
|
|
11
|
+
week: 3600000 * 24 * 7,
|
|
12
|
+
month: 3600000 * 24 * 30,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
var instances = []
|
|
16
|
+
|
|
17
|
+
var matches = function (a) {
|
|
18
|
+
return function (b) {
|
|
19
|
+
return a === b
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var doesntMatch = function (a) {
|
|
24
|
+
return function (b) {
|
|
25
|
+
return !matches(a)(b)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var logDuration = function (d, prefix) {
|
|
30
|
+
var str = d > 1000 ? (d / 1000).toFixed(2) + 'sec' : d + 'ms'
|
|
31
|
+
return '\x1b[33m- ' + (prefix ? prefix + ' ' : '') + str + '\x1b[0m'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getSafeHeaders(res) {
|
|
35
|
+
return res.getHeaders ? res.getHeaders() : res._headers
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ApiCache() {
|
|
39
|
+
var memCache = new MemoryCache()
|
|
40
|
+
|
|
41
|
+
var globalOptions = {
|
|
42
|
+
debug: false,
|
|
43
|
+
defaultDuration: 3600000,
|
|
44
|
+
enabled: true,
|
|
45
|
+
appendKey: [],
|
|
46
|
+
jsonp: false,
|
|
47
|
+
redisClient: false,
|
|
48
|
+
headerBlacklist: [],
|
|
49
|
+
statusCodes: {
|
|
50
|
+
include: [],
|
|
51
|
+
exclude: [],
|
|
52
|
+
},
|
|
53
|
+
events: {
|
|
54
|
+
expire: undefined,
|
|
55
|
+
},
|
|
56
|
+
headers: {
|
|
57
|
+
// 'cache-control': 'no-cache' // example of header overwrite
|
|
58
|
+
},
|
|
59
|
+
trackPerformance: false,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var middlewareOptions = []
|
|
63
|
+
var instance = this
|
|
64
|
+
var index = null
|
|
65
|
+
var timers = {}
|
|
66
|
+
var performanceArray = [] // for tracking cache hit rate
|
|
67
|
+
|
|
68
|
+
instances.push(this)
|
|
69
|
+
this.id = instances.length
|
|
70
|
+
|
|
71
|
+
function debug(a, b, c, d) {
|
|
72
|
+
var arr = ['\x1b[36m[apicache]\x1b[0m', a, b, c, d].filter(function (arg) {
|
|
73
|
+
return arg !== undefined
|
|
74
|
+
})
|
|
75
|
+
var debugEnv =
|
|
76
|
+
process.env.DEBUG &&
|
|
77
|
+
process.env.DEBUG.split(',').indexOf('apicache') !== -1
|
|
78
|
+
|
|
79
|
+
return (globalOptions.debug || debugEnv) && console.log.apply(null, arr)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function shouldCacheResponse(request, response, toggle) {
|
|
83
|
+
var opt = globalOptions
|
|
84
|
+
var codes = opt.statusCodes
|
|
85
|
+
|
|
86
|
+
if (!response) return false
|
|
87
|
+
|
|
88
|
+
if (toggle && !toggle(request, response)) {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
codes.exclude &&
|
|
94
|
+
codes.exclude.length &&
|
|
95
|
+
codes.exclude.indexOf(response.statusCode) !== -1
|
|
96
|
+
)
|
|
97
|
+
return false
|
|
98
|
+
if (
|
|
99
|
+
codes.include &&
|
|
100
|
+
codes.include.length &&
|
|
101
|
+
codes.include.indexOf(response.statusCode) === -1
|
|
102
|
+
)
|
|
103
|
+
return false
|
|
104
|
+
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function addIndexEntries(key, req) {
|
|
109
|
+
var groupName = req.apicacheGroup
|
|
110
|
+
|
|
111
|
+
if (groupName) {
|
|
112
|
+
debug('group detected "' + groupName + '"')
|
|
113
|
+
var group = (index.groups[groupName] = index.groups[groupName] || [])
|
|
114
|
+
group.unshift(key)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
index.all.unshift(key)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function filterBlacklistedHeaders(headers) {
|
|
121
|
+
return Object.keys(headers)
|
|
122
|
+
.filter(function (key) {
|
|
123
|
+
return globalOptions.headerBlacklist.indexOf(key) === -1
|
|
124
|
+
})
|
|
125
|
+
.reduce(function (acc, header) {
|
|
126
|
+
acc[header] = headers[header]
|
|
127
|
+
return acc
|
|
128
|
+
}, {})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createCacheObject(status, headers, data, encoding) {
|
|
132
|
+
return {
|
|
133
|
+
status: status,
|
|
134
|
+
headers: filterBlacklistedHeaders(headers),
|
|
135
|
+
data: data,
|
|
136
|
+
encoding: encoding,
|
|
137
|
+
timestamp: new Date().getTime() / 1000, // seconds since epoch. This is used to properly decrement max-age headers in cached responses.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function cacheResponse(key, value, duration) {
|
|
142
|
+
var redis = globalOptions.redisClient
|
|
143
|
+
var expireCallback = globalOptions.events.expire
|
|
144
|
+
|
|
145
|
+
if (redis && redis.connected) {
|
|
146
|
+
try {
|
|
147
|
+
redis.hset(key, 'response', JSON.stringify(value))
|
|
148
|
+
redis.hset(key, 'duration', duration)
|
|
149
|
+
redis.expire(key, duration / 1000, expireCallback || function () {})
|
|
150
|
+
} catch (err) {
|
|
151
|
+
debug('[apicache] error in redis.hset()')
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
memCache.add(key, value, duration, expireCallback)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// add automatic cache clearing from duration, includes max limit on setTimeout
|
|
158
|
+
timers[key] = setTimeout(function () {
|
|
159
|
+
instance.clear(key, true)
|
|
160
|
+
}, Math.min(duration, 2147483647))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function accumulateContent(res, content) {
|
|
164
|
+
if (content) {
|
|
165
|
+
if (typeof content == 'string') {
|
|
166
|
+
res._apicache.content = (res._apicache.content || '') + content
|
|
167
|
+
} else if (Buffer.isBuffer(content)) {
|
|
168
|
+
var oldContent = res._apicache.content
|
|
169
|
+
|
|
170
|
+
if (typeof oldContent === 'string') {
|
|
171
|
+
oldContent = !Buffer.from
|
|
172
|
+
? new Buffer(oldContent)
|
|
173
|
+
: Buffer.from(oldContent)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!oldContent) {
|
|
177
|
+
oldContent = !Buffer.alloc ? new Buffer(0) : Buffer.alloc(0)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
res._apicache.content = Buffer.concat(
|
|
181
|
+
[oldContent, content],
|
|
182
|
+
oldContent.length + content.length,
|
|
183
|
+
)
|
|
184
|
+
} else {
|
|
185
|
+
res._apicache.content = content
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function makeResponseCacheable(
|
|
191
|
+
req,
|
|
192
|
+
res,
|
|
193
|
+
next,
|
|
194
|
+
key,
|
|
195
|
+
duration,
|
|
196
|
+
strDuration,
|
|
197
|
+
toggle,
|
|
198
|
+
) {
|
|
199
|
+
// monkeypatch res.end to create cache object
|
|
200
|
+
res._apicache = {
|
|
201
|
+
write: res.write,
|
|
202
|
+
writeHead: res.writeHead,
|
|
203
|
+
end: res.end,
|
|
204
|
+
cacheable: true,
|
|
205
|
+
content: undefined,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// append header overwrites if applicable
|
|
209
|
+
Object.keys(globalOptions.headers).forEach(function (name) {
|
|
210
|
+
res.setHeader(name, globalOptions.headers[name])
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
res.writeHead = function () {
|
|
214
|
+
// add cache control headers
|
|
215
|
+
if (!globalOptions.headers['cache-control']) {
|
|
216
|
+
if (shouldCacheResponse(req, res, toggle)) {
|
|
217
|
+
res.setHeader(
|
|
218
|
+
'cache-control',
|
|
219
|
+
'max-age=' + (duration / 1000).toFixed(0),
|
|
220
|
+
)
|
|
221
|
+
} else {
|
|
222
|
+
res.setHeader('cache-control', 'no-cache, no-store, must-revalidate')
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
res._apicache.headers = Object.assign({}, getSafeHeaders(res))
|
|
227
|
+
return res._apicache.writeHead.apply(this, arguments)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// patch res.write
|
|
231
|
+
res.write = function (content) {
|
|
232
|
+
accumulateContent(res, content)
|
|
233
|
+
return res._apicache.write.apply(this, arguments)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// patch res.end
|
|
237
|
+
res.end = function (content, encoding) {
|
|
238
|
+
if (shouldCacheResponse(req, res, toggle)) {
|
|
239
|
+
accumulateContent(res, content)
|
|
240
|
+
|
|
241
|
+
if (res._apicache.cacheable && res._apicache.content) {
|
|
242
|
+
addIndexEntries(key, req)
|
|
243
|
+
var headers = res._apicache.headers || getSafeHeaders(res)
|
|
244
|
+
var cacheObject = createCacheObject(
|
|
245
|
+
res.statusCode,
|
|
246
|
+
headers,
|
|
247
|
+
res._apicache.content,
|
|
248
|
+
encoding,
|
|
249
|
+
)
|
|
250
|
+
cacheResponse(key, cacheObject, duration)
|
|
251
|
+
|
|
252
|
+
// display log entry
|
|
253
|
+
var elapsed = new Date() - req.apicacheTimer
|
|
254
|
+
debug(
|
|
255
|
+
'adding cache entry for "' + key + '" @ ' + strDuration,
|
|
256
|
+
logDuration(elapsed),
|
|
257
|
+
)
|
|
258
|
+
debug('_apicache.headers: ', res._apicache.headers)
|
|
259
|
+
debug('res.getHeaders(): ', getSafeHeaders(res))
|
|
260
|
+
debug('cacheObject: ', cacheObject)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return res._apicache.end.apply(this, arguments)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
next()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function sendCachedResponse(
|
|
271
|
+
request,
|
|
272
|
+
response,
|
|
273
|
+
cacheObject,
|
|
274
|
+
toggle,
|
|
275
|
+
next,
|
|
276
|
+
duration,
|
|
277
|
+
) {
|
|
278
|
+
if (toggle && !toggle(request, response)) {
|
|
279
|
+
return next()
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
var headers = getSafeHeaders(response)
|
|
283
|
+
|
|
284
|
+
Object.assign(
|
|
285
|
+
headers,
|
|
286
|
+
filterBlacklistedHeaders(cacheObject.headers || {}),
|
|
287
|
+
{
|
|
288
|
+
// set properly-decremented max-age header. This ensures that max-age is in sync with the cache expiration.
|
|
289
|
+
'cache-control':
|
|
290
|
+
'max-age=' +
|
|
291
|
+
Math.max(
|
|
292
|
+
0,
|
|
293
|
+
(
|
|
294
|
+
duration / 1000 -
|
|
295
|
+
(new Date().getTime() / 1000 - cacheObject.timestamp)
|
|
296
|
+
).toFixed(0),
|
|
297
|
+
),
|
|
298
|
+
},
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
// only embed apicache headers when not in production environment
|
|
302
|
+
|
|
303
|
+
// unstringify buffers
|
|
304
|
+
var data = cacheObject.data
|
|
305
|
+
if (data && data.type === 'Buffer') {
|
|
306
|
+
data =
|
|
307
|
+
typeof data.data === 'number'
|
|
308
|
+
? new Buffer.alloc(data.data)
|
|
309
|
+
: new Buffer.from(data.data)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// test Etag against If-None-Match for 304
|
|
313
|
+
var cachedEtag = cacheObject.headers.etag
|
|
314
|
+
var requestEtag = request.headers['if-none-match']
|
|
315
|
+
|
|
316
|
+
if (requestEtag && cachedEtag === requestEtag) {
|
|
317
|
+
response.writeHead(304, headers)
|
|
318
|
+
return response.end()
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
response.writeHead(cacheObject.status || 200, headers)
|
|
322
|
+
|
|
323
|
+
return response.end(data, cacheObject.encoding)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function syncOptions() {
|
|
327
|
+
for (var i in middlewareOptions) {
|
|
328
|
+
Object.assign(
|
|
329
|
+
middlewareOptions[i].options,
|
|
330
|
+
globalOptions,
|
|
331
|
+
middlewareOptions[i].localOptions,
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.clear = function (target, isAutomatic) {
|
|
337
|
+
var group = index.groups[target]
|
|
338
|
+
var redis = globalOptions.redisClient
|
|
339
|
+
|
|
340
|
+
if (group) {
|
|
341
|
+
debug('clearing group "' + target + '"')
|
|
342
|
+
|
|
343
|
+
group.forEach(function (key) {
|
|
344
|
+
debug('clearing cached entry for "' + key + '"')
|
|
345
|
+
clearTimeout(timers[key])
|
|
346
|
+
delete timers[key]
|
|
347
|
+
if (!globalOptions.redisClient) {
|
|
348
|
+
memCache.delete(key)
|
|
349
|
+
} else {
|
|
350
|
+
try {
|
|
351
|
+
redis.del(key)
|
|
352
|
+
} catch (err) {
|
|
353
|
+
logger.info('[apicache] error in redis.del("' + key + '")')
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
index.all = index.all.filter(doesntMatch(key))
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
delete index.groups[target]
|
|
360
|
+
} else if (target) {
|
|
361
|
+
debug(
|
|
362
|
+
'clearing ' +
|
|
363
|
+
(isAutomatic ? 'expired' : 'cached') +
|
|
364
|
+
' entry for "' +
|
|
365
|
+
target +
|
|
366
|
+
'"',
|
|
367
|
+
)
|
|
368
|
+
clearTimeout(timers[target])
|
|
369
|
+
delete timers[target]
|
|
370
|
+
// clear actual cached entry
|
|
371
|
+
if (!redis) {
|
|
372
|
+
memCache.delete(target)
|
|
373
|
+
} else {
|
|
374
|
+
try {
|
|
375
|
+
redis.del(target)
|
|
376
|
+
} catch (err) {
|
|
377
|
+
logger.info('[apicache] error in redis.del("' + target + '")')
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// remove from global index
|
|
382
|
+
index.all = index.all.filter(doesntMatch(target))
|
|
383
|
+
|
|
384
|
+
// remove target from each group that it may exist in
|
|
385
|
+
Object.keys(index.groups).forEach(function (groupName) {
|
|
386
|
+
index.groups[groupName] = index.groups[groupName].filter(
|
|
387
|
+
doesntMatch(target),
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
// delete group if now empty
|
|
391
|
+
if (!index.groups[groupName].length) {
|
|
392
|
+
delete index.groups[groupName]
|
|
393
|
+
}
|
|
394
|
+
})
|
|
395
|
+
} else {
|
|
396
|
+
debug('clearing entire index')
|
|
397
|
+
|
|
398
|
+
if (!redis) {
|
|
399
|
+
memCache.clear()
|
|
400
|
+
} else {
|
|
401
|
+
// clear redis keys one by one from internal index to prevent clearing non-apicache entries
|
|
402
|
+
index.all.forEach(function (key) {
|
|
403
|
+
clearTimeout(timers[key])
|
|
404
|
+
delete timers[key]
|
|
405
|
+
try {
|
|
406
|
+
redis.del(key)
|
|
407
|
+
} catch (err) {
|
|
408
|
+
logger.info('[apicache] error in redis.del("' + key + '")')
|
|
409
|
+
}
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
this.resetIndex()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return this.getIndex()
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function parseDuration(duration, defaultDuration) {
|
|
419
|
+
if (typeof duration === 'number') return duration
|
|
420
|
+
|
|
421
|
+
if (typeof duration === 'string') {
|
|
422
|
+
var split = duration.match(/^([\d\.,]+)\s?(\w+)$/)
|
|
423
|
+
|
|
424
|
+
if (split.length === 3) {
|
|
425
|
+
var len = parseFloat(split[1])
|
|
426
|
+
var unit = split[2].replace(/s$/i, '').toLowerCase()
|
|
427
|
+
if (unit === 'm') {
|
|
428
|
+
unit = 'ms'
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return (len || 1) * (t[unit] || 0)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return defaultDuration
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
this.getDuration = function (duration) {
|
|
439
|
+
return parseDuration(duration, globalOptions.defaultDuration)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Return cache performance statistics (hit rate). Suitable for putting into a route:
|
|
444
|
+
* <code>
|
|
445
|
+
* app.get('/api/cache/performance', (req, res) => {
|
|
446
|
+
* res.json(apicache.getPerformance())
|
|
447
|
+
* })
|
|
448
|
+
* </code>
|
|
449
|
+
*/
|
|
450
|
+
this.getPerformance = function () {
|
|
451
|
+
return performanceArray.map(function (p) {
|
|
452
|
+
return p.report()
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
this.getIndex = function (group) {
|
|
457
|
+
if (group) {
|
|
458
|
+
return index.groups[group]
|
|
459
|
+
} else {
|
|
460
|
+
return index
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
this.middleware = function cache(
|
|
465
|
+
strDuration,
|
|
466
|
+
middlewareToggle,
|
|
467
|
+
localOptions,
|
|
468
|
+
) {
|
|
469
|
+
var duration = instance.getDuration(strDuration)
|
|
470
|
+
var opt = {}
|
|
471
|
+
|
|
472
|
+
middlewareOptions.push({
|
|
473
|
+
options: opt,
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
var options = function (localOptions) {
|
|
477
|
+
if (localOptions) {
|
|
478
|
+
middlewareOptions.find(function (middleware) {
|
|
479
|
+
return middleware.options === opt
|
|
480
|
+
}).localOptions = localOptions
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
syncOptions()
|
|
484
|
+
|
|
485
|
+
return opt
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
options(localOptions)
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* A Function for non tracking performance
|
|
492
|
+
*/
|
|
493
|
+
function NOOPCachePerformance() {
|
|
494
|
+
this.report = this.hit = this.miss = function () {} // noop;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* A function for tracking and reporting hit rate. These statistics are returned by the getPerformance() call above.
|
|
499
|
+
*/
|
|
500
|
+
function CachePerformance() {
|
|
501
|
+
/**
|
|
502
|
+
* Tracks the hit rate for the last 100 requests.
|
|
503
|
+
* If there have been fewer than 100 requests, the hit rate just considers the requests that have happened.
|
|
504
|
+
*/
|
|
505
|
+
this.hitsLast100 = new Uint8Array(100 / 4) // each hit is 2 bits
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Tracks the hit rate for the last 1000 requests.
|
|
509
|
+
* If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened.
|
|
510
|
+
*/
|
|
511
|
+
this.hitsLast1000 = new Uint8Array(1000 / 4) // each hit is 2 bits
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Tracks the hit rate for the last 10000 requests.
|
|
515
|
+
* If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened.
|
|
516
|
+
*/
|
|
517
|
+
this.hitsLast10000 = new Uint8Array(10000 / 4) // each hit is 2 bits
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Tracks the hit rate for the last 100000 requests.
|
|
521
|
+
* If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened.
|
|
522
|
+
*/
|
|
523
|
+
this.hitsLast100000 = new Uint8Array(100000 / 4) // each hit is 2 bits
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* The number of calls that have passed through the middleware since the server started.
|
|
527
|
+
*/
|
|
528
|
+
this.callCount = 0
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* The total number of hits since the server started
|
|
532
|
+
*/
|
|
533
|
+
this.hitCount = 0
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* The key from the last cache hit. This is useful in identifying which route these statistics apply to.
|
|
537
|
+
*/
|
|
538
|
+
this.lastCacheHit = null
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* The key from the last cache miss. This is useful in identifying which route these statistics apply to.
|
|
542
|
+
*/
|
|
543
|
+
this.lastCacheMiss = null
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Return performance statistics
|
|
547
|
+
*/
|
|
548
|
+
this.report = function () {
|
|
549
|
+
return {
|
|
550
|
+
lastCacheHit: this.lastCacheHit,
|
|
551
|
+
lastCacheMiss: this.lastCacheMiss,
|
|
552
|
+
callCount: this.callCount,
|
|
553
|
+
hitCount: this.hitCount,
|
|
554
|
+
missCount: this.callCount - this.hitCount,
|
|
555
|
+
hitRate: this.callCount == 0 ? null : this.hitCount / this.callCount,
|
|
556
|
+
hitRateLast100: this.hitRate(this.hitsLast100),
|
|
557
|
+
hitRateLast1000: this.hitRate(this.hitsLast1000),
|
|
558
|
+
hitRateLast10000: this.hitRate(this.hitsLast10000),
|
|
559
|
+
hitRateLast100000: this.hitRate(this.hitsLast100000),
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Computes a cache hit rate from an array of hits and misses.
|
|
565
|
+
* @param {Uint8Array} array An array representing hits and misses.
|
|
566
|
+
* @returns a number between 0 and 1, or null if the array has no hits or misses
|
|
567
|
+
*/
|
|
568
|
+
this.hitRate = function (array) {
|
|
569
|
+
var hits = 0
|
|
570
|
+
var misses = 0
|
|
571
|
+
for (var i = 0; i < array.length; i++) {
|
|
572
|
+
var n8 = array[i]
|
|
573
|
+
for (j = 0; j < 4; j++) {
|
|
574
|
+
switch (n8 & 3) {
|
|
575
|
+
case 1:
|
|
576
|
+
hits++
|
|
577
|
+
break
|
|
578
|
+
case 2:
|
|
579
|
+
misses++
|
|
580
|
+
break
|
|
581
|
+
}
|
|
582
|
+
n8 >>= 2
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
var total = hits + misses
|
|
586
|
+
if (total == 0) return null
|
|
587
|
+
return hits / total
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Record a hit or miss in the given array. It will be recorded at a position determined
|
|
592
|
+
* by the current value of the callCount variable.
|
|
593
|
+
* @param {Uint8Array} array An array representing hits and misses.
|
|
594
|
+
* @param {boolean} hit true for a hit, false for a miss
|
|
595
|
+
* Each element in the array is 8 bits, and encodes 4 hit/miss records.
|
|
596
|
+
* Each hit or miss is encoded as to bits as follows:
|
|
597
|
+
* 00 means no hit or miss has been recorded in these bits
|
|
598
|
+
* 01 encodes a hit
|
|
599
|
+
* 10 encodes a miss
|
|
600
|
+
*/
|
|
601
|
+
this.recordHitInArray = function (array, hit) {
|
|
602
|
+
var arrayIndex = ~~(this.callCount / 4) % array.length
|
|
603
|
+
var bitOffset = (this.callCount % 4) * 2 // 2 bits per record, 4 records per uint8 array element
|
|
604
|
+
var clearMask = ~(3 << bitOffset)
|
|
605
|
+
var record = (hit ? 1 : 2) << bitOffset
|
|
606
|
+
array[arrayIndex] = (array[arrayIndex] & clearMask) | record
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Records the hit or miss in the tracking arrays and increments the call count.
|
|
611
|
+
* @param {boolean} hit true records a hit, false records a miss
|
|
612
|
+
*/
|
|
613
|
+
this.recordHit = function (hit) {
|
|
614
|
+
this.recordHitInArray(this.hitsLast100, hit)
|
|
615
|
+
this.recordHitInArray(this.hitsLast1000, hit)
|
|
616
|
+
this.recordHitInArray(this.hitsLast10000, hit)
|
|
617
|
+
this.recordHitInArray(this.hitsLast100000, hit)
|
|
618
|
+
if (hit) this.hitCount++
|
|
619
|
+
this.callCount++
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Records a hit event, setting lastCacheMiss to the given key
|
|
624
|
+
* @param {string} key The key that had the cache hit
|
|
625
|
+
*/
|
|
626
|
+
this.hit = function (key) {
|
|
627
|
+
this.recordHit(true)
|
|
628
|
+
this.lastCacheHit = key
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Records a miss event, setting lastCacheMiss to the given key
|
|
633
|
+
* @param {string} key The key that had the cache miss
|
|
634
|
+
*/
|
|
635
|
+
this.miss = function (key) {
|
|
636
|
+
this.recordHit(false)
|
|
637
|
+
this.lastCacheMiss = key
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
var perf = globalOptions.trackPerformance
|
|
642
|
+
? new CachePerformance()
|
|
643
|
+
: new NOOPCachePerformance()
|
|
644
|
+
|
|
645
|
+
performanceArray.push(perf)
|
|
646
|
+
|
|
647
|
+
var cache = function (req, res, next) {
|
|
648
|
+
function bypass() {
|
|
649
|
+
debug('bypass detected, skipping cache.')
|
|
650
|
+
return next()
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// initial bypass chances
|
|
654
|
+
if (!opt.enabled) return bypass()
|
|
655
|
+
if (
|
|
656
|
+
req.headers['x-apicache-bypass'] ||
|
|
657
|
+
req.headers['x-apicache-force-fetch']
|
|
658
|
+
)
|
|
659
|
+
return bypass()
|
|
660
|
+
|
|
661
|
+
// REMOVED IN 0.11.1 TO CORRECT MIDDLEWARE TOGGLE EXECUTE ORDER
|
|
662
|
+
// if (typeof middlewareToggle === 'function') {
|
|
663
|
+
// if (!middlewareToggle(req, res)) return bypass()
|
|
664
|
+
// } else if (middlewareToggle !== undefined && !middlewareToggle) {
|
|
665
|
+
// return bypass()
|
|
666
|
+
// }
|
|
667
|
+
|
|
668
|
+
// embed timer
|
|
669
|
+
req.apicacheTimer = new Date()
|
|
670
|
+
|
|
671
|
+
// In Express 4.x the url is ambigious based on where a router is mounted. originalUrl will give the full Url
|
|
672
|
+
var key =
|
|
673
|
+
req.hostname +
|
|
674
|
+
(req.originalUrl || req.url) +
|
|
675
|
+
JSON.stringify(req.cookies)
|
|
676
|
+
// Remove querystring from key if jsonp option is enabled
|
|
677
|
+
if (opt.jsonp) {
|
|
678
|
+
key = url.parse(key).pathname
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// add appendKey (either custom function or response path)
|
|
682
|
+
if (typeof opt.appendKey === 'function') {
|
|
683
|
+
key += '$$appendKey=' + opt.appendKey(req, res)
|
|
684
|
+
} else if (opt.appendKey.length > 0) {
|
|
685
|
+
var appendKey = req
|
|
686
|
+
|
|
687
|
+
for (var i = 0; i < opt.appendKey.length; i++) {
|
|
688
|
+
appendKey = appendKey[opt.appendKey[i]]
|
|
689
|
+
}
|
|
690
|
+
key += '$$appendKey=' + appendKey
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// attempt cache hit
|
|
694
|
+
var redis = opt.redisClient
|
|
695
|
+
var cached = !redis ? memCache.getValue(key) : null
|
|
696
|
+
|
|
697
|
+
// send if cache hit from memory-cache
|
|
698
|
+
if (cached) {
|
|
699
|
+
var elapsed = new Date() - req.apicacheTimer
|
|
700
|
+
debug(
|
|
701
|
+
'sending cached (memory-cache) version of',
|
|
702
|
+
key,
|
|
703
|
+
logDuration(elapsed),
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
perf.hit(key)
|
|
707
|
+
return sendCachedResponse(
|
|
708
|
+
req,
|
|
709
|
+
res,
|
|
710
|
+
cached,
|
|
711
|
+
middlewareToggle,
|
|
712
|
+
next,
|
|
713
|
+
duration,
|
|
714
|
+
)
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// send if cache hit from redis
|
|
718
|
+
if (redis && redis.connected) {
|
|
719
|
+
try {
|
|
720
|
+
redis.hgetall(key, function (err, obj) {
|
|
721
|
+
if (!err && obj && obj.response) {
|
|
722
|
+
var elapsed = new Date() - req.apicacheTimer
|
|
723
|
+
debug(
|
|
724
|
+
'sending cached (redis) version of',
|
|
725
|
+
key,
|
|
726
|
+
logDuration(elapsed),
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
perf.hit(key)
|
|
730
|
+
return sendCachedResponse(
|
|
731
|
+
req,
|
|
732
|
+
res,
|
|
733
|
+
JSON.parse(obj.response),
|
|
734
|
+
middlewareToggle,
|
|
735
|
+
next,
|
|
736
|
+
duration,
|
|
737
|
+
)
|
|
738
|
+
} else {
|
|
739
|
+
perf.miss(key)
|
|
740
|
+
return makeResponseCacheable(
|
|
741
|
+
req,
|
|
742
|
+
res,
|
|
743
|
+
next,
|
|
744
|
+
key,
|
|
745
|
+
duration,
|
|
746
|
+
strDuration,
|
|
747
|
+
middlewareToggle,
|
|
748
|
+
)
|
|
749
|
+
}
|
|
750
|
+
})
|
|
751
|
+
} catch (err) {
|
|
752
|
+
// bypass redis on error
|
|
753
|
+
perf.miss(key)
|
|
754
|
+
return makeResponseCacheable(
|
|
755
|
+
req,
|
|
756
|
+
res,
|
|
757
|
+
next,
|
|
758
|
+
key,
|
|
759
|
+
duration,
|
|
760
|
+
strDuration,
|
|
761
|
+
middlewareToggle,
|
|
762
|
+
)
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
perf.miss(key)
|
|
766
|
+
return makeResponseCacheable(
|
|
767
|
+
req,
|
|
768
|
+
res,
|
|
769
|
+
next,
|
|
770
|
+
key,
|
|
771
|
+
duration,
|
|
772
|
+
strDuration,
|
|
773
|
+
middlewareToggle,
|
|
774
|
+
)
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
cache.options = options
|
|
779
|
+
|
|
780
|
+
return cache
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
this.options = function (options) {
|
|
784
|
+
if (options) {
|
|
785
|
+
Object.assign(globalOptions, options)
|
|
786
|
+
syncOptions()
|
|
787
|
+
|
|
788
|
+
if ('defaultDuration' in options) {
|
|
789
|
+
// Convert the default duration to a number in milliseconds (if needed)
|
|
790
|
+
globalOptions.defaultDuration = parseDuration(
|
|
791
|
+
globalOptions.defaultDuration,
|
|
792
|
+
3600000,
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (globalOptions.trackPerformance) {
|
|
797
|
+
debug(
|
|
798
|
+
'WARNING: using trackPerformance flag can cause high memory usage!',
|
|
799
|
+
)
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return this
|
|
803
|
+
} else {
|
|
804
|
+
return globalOptions
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
this.resetIndex = function () {
|
|
809
|
+
index = {
|
|
810
|
+
all: [],
|
|
811
|
+
groups: {},
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
this.newInstance = function (config) {
|
|
816
|
+
var instance = new ApiCache()
|
|
817
|
+
|
|
818
|
+
if (config) {
|
|
819
|
+
instance.options(config)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return instance
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
this.clone = function () {
|
|
826
|
+
return this.newInstance(this.options())
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// initialize index
|
|
830
|
+
this.resetIndex()
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
module.exports = new ApiCache()
|