- 記事一覧 >
- ブログ記事
Next.jsのfetchとstatic-tweetのfetchのプロキシ対応
はじめに
このブログのソースコード、npm run dev
で プロキシサーバー越しにmicroCMSとTwitterのAPIをGETできるようになっています。
microCMS APIの方は、fetch
関数部分をプロキシ環境変数を使うように書き換えて対応しました。
Twitter APIの方は、npmのreact-static-tweets(GitHub)からGETされていて、自前のソースコードではなく、対応に困りました。
react-static-tweets
は、static-tweet(GitHub)を使ってます。
最終的に、Twitter APIの方は、patch-package
を使ってnode_modules/static-tweets
を書き換えて解決しました。
今回は、fetch
のProxy対応を主題とし、patch-package
主題の話と2回に分けて書きたいと思います。
組み込みのfetchの変更
以下のようにhttps-proxy-agent
を使って、対処しました。process.env.https_proxy
(環境変数のhttps_proxy
)が有ったら、fetch
の第二引数を
{
headers: header,
agent: new HttpsProxyAgent(プロキシサーバー),
}
のようにagent:
キーを追加する感じです。
プロキシサーバーの部分は、http://proxyserver.example:3128
のような文字列になります。
なお、process.env.https_proxy
は、.env.local
に
https_proxy=http://proxyserver.example:3128
と書いてもOKです。
これが無い場合、OSの環境変数https_proxy
が採用されます。
diff(一例です。):
@@ -20,6 +20,7 @@ import useMobileDevice from '../../hooks/useMobileDevice';
|
|
20
20
|
import MobileShare from '../../components/mobileShare';
|
21
21
|
import tocbot from 'tocbot';
|
22
22
|
import { OpenGraphImages } from 'next-seo/lib/types';
|
23
|
+ import { HttpsProxyAgent } from 'https-proxy-agent';
|
23
24
|
|
24
25
|
interface Tweet {
|
25
26
|
id: string;
|
@@ -420,8 +420,12 @@ export const getStaticProps: GetStaticProps<Props, Slug> = async ({
|
|
420
420
|
|
421
421
|
const header: HeadersInit = new Headers();
|
422
422
|
header.set('X-API-KEY', process.env.API_KEY || '');
|
423
|
- const
|
423
|
+ const proxy = process.env.https_proxy;
|
424
|
+ const key = proxy ? {
|
424
425
|
headers: header,
|
426
|
+ agent: new HttpsProxyAgent(proxy)
|
427
|
+ } : {
|
428
|
+ headers: header
|
425
429
|
};
|
426
430
|
const data: Content = await fetch(`${process.env.API_URL}contents/` + id, key)
|
427
431
|
.then((res) => res.json())
|
@@ -454,8 +458,12 @@ export const getStaticProps: GetStaticProps<Props, Slug> = async ({
|
|
454
458
|
export const getStaticPaths: GetStaticPaths<Slug> = async () => {
|
455
459
|
const header: HeadersInit = new Headers();
|
456
460
|
header.set('X-API-KEY', process.env.API_KEY || '');
|
457
|
- const
|
461
|
+ const proxy = process.env.https_proxy;
|
462
|
+ const key = proxy ? {
|
458
463
|
headers: header,
|
464
|
+ agent: new HttpsProxyAgent(proxy)
|
465
|
+ } : {
|
466
|
+ headers: header
|
459
467
|
};
|
460
468
|
const data: ContentRootObject = await fetch(
|
461
469
|
`${process.env.API_URL}contents?limit=9999&fields=id`,
|
react-static-tweetsについて
ビルド時にツイッターのデータを取得して、HTMLとして生成しておけるものです。これにより、表示するときは、ただのHTMLですので、負荷を減らせます。
$ npm install react-static-tweets
でインストールして、
import { fetchTweetAst } from 'static-tweets';
getStaticProps
内で、
const tweets = await Promise.all(
twitter_ids.map(async (id: string) => {
const ast = await fetchTweetAst(id);
return { id, ast };
})
);
のようにTweetIDを渡して、ツィートデータ(解析結果)を取得しています。
それをさらに、
import { Tweet } from 'react-static-tweets';
(略)
{tweets.map((tweet) => {
return <Tweet key={tweet.id} id={tweet.id} ast={tweet.ast} />;
})}
(略)
static-tweetsのfetchについて
プロキシ環境変数をセットしただけで、起動すると、Twitter APIへのGETでエラーになります。
$ export https_proxy="http://192.168.0.158:3128"
$ export HTTPS_PROXY="http://192.168.0.158:3128"
$ npm run dev
Error: queryA EREFUSED syndication.twitter.com
at QueryReqWrap.onresolve [as oncomplete] (dns.js:203:19) {
errno: undefined,
code: 'EREFUSED',
syscall: 'queryA',
hostname: 'syndication.twitter.com',
url: 'https://syndication.twitter.com/tweets.json?ids=1411580890468077575',
opts: {
agent: HttpsAgent {
_events: [Object: null prototype],
_eventsCount: 4,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: [Object],
requests: {},
sockets: {},
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: true,
maxSockets: Infinity,
maxFreeSockets: 256,
freeSocketKeepAliveTimeout: 15000,
timeout: 30000,
socketActiveTTL: null,
createSocketCount: 0,
createSocketCountLastCheck: 0,
createSocketErrorCount: 0,
createSocketErrorCountLastCheck: 0,
closeSocketCount: 0,
closeSocketCountLastCheck: 0,
errorSocketCount: 0,
errorSocketCountLastCheck: 0,
requestCount: 0,
requestCountLastCheck: 0,
timeoutSocketCount: 0,
timeoutSocketCountLastCheck: 0,
maxCachedSessions: 100,
_sessionCache: [Object],
[Symbol(kCapture)]: false
},
redirect: 'manual',
headers: Headers { [Symbol(map)]: [Object: null prototype] },
onRedirect: [Function (anonymous)]
}
}
react-static-tweets
のpackages/static-tweets/src/fetchTweetAst.ts
から
export async function fetchTweetAst(tweetId: string): Promise<any> {
const tweetHtml = await fetchTweetHtml(tweetId)
↓
packages/static-tweets/src/twitter/api.ts
export async function fetchTweetsHtml(ids) {
const res = await fetch(`${SYNDICATION_URL}/tweets.json?ids=${ids}`)
のfetch
でエラーになっていました。
このfetch
が何者かと言うと、
packages/static-tweets/src/twitter/api.ts
import fetch from '../fetch'
で取り込まれていて、
packages/static-tweets/src/fetch.ts
import vercelFetch from '@vercel/fetch'
export default vercelFetch()
となっていますので、@vercel/fetch
でした。
そして、https-proxy-agent
を使えば良いのかなと思いましたが、Next.jsのfetch
と@vercel/fetch
とは別物で、同じ手法は使えませんでした。
getStaticProps
内のfetch
: [Function: fetchWithAgent]fetchTweetsHtml
のfetch
: [AsyncFunction: vercelFetch]
Next.jsのソースコード(GitHub)のfetchWithAgent
を見ると、
packages/next/server/node-polyfill-fetch.js
import fetch, { Headers, Request, Response } from 'node-fetch'
// Polyfill fetch() in the Node.js environment
if (!global.fetch) {
const agent = ({ protocol }) =>
protocol === 'http:' ? global.__NEXT_HTTP_AGENT : global.__NEXT_HTTPS_AGENT
const fetchWithAgent = (url, opts, ...rest) => {
if (!opts) {
opts = { agent }
} else if (!opts.agent) {
opts.agent = agent
}
return fetch(url, opts, ...rest)
}
global.fetch = fetchWithAgent
global.Headers = Headers
global.Request = Request
global.Response = Response
}
となっていて、node-fetch
を使っています。
static-tweets変更
packages/static-tweets/src/twitter/api.ts
import fetch from '../fetch'
をコメントアウトして、agentを受け取るisomorphic-fetch
を使うことにしました。
サーバーサイド(ビルド時)でしか
fetch
していないはずですので、node-fetch
で良かったかもしれません。実際特に問題無かったです。
diff:
@@ -1,4 +1,6 @@
|
|
1
|
- import fetch from '../fetch'
|
1
|
+ //import fetch from '../fetch'
|
2
|
+ import fetch from 'isomorphic-fetch';
|
3
|
+ import { HttpsProxyAgent } from 'https-proxy-agent';
|
2
4
|
|
3
5
|
const API_URL = 'https://api.twitter.com'
|
4
6
|
const SYNDICATION_URL = 'https://syndication.twitter.com'
|
@@ -13,7 +15,8 @@ function twitterLabsEnabled(expansions) {
|
|
13
15
|
}
|
14
16
|
|
15
17
|
export async function fetchTweetsHtml(ids) {
|
16
|
- const
|
18
|
+ const proxy = process.env.https_proxy;
|
19
|
+ const res = await fetch(`${SYNDICATION_URL}/tweets.json?ids=${ids}`, proxy ? { agent: new HttpsProxyAgent(proxy) } : {})
|
17
20
|
|
18
21
|
if (res.ok) return res.json()
|
19
22
|
if (res.status === 404) return {}
|
@@ -32,14 +35,17 @@ export async function fetchUserStatus(tweetId) {
|
|
32
35
|
// If there isn't an API token don't do anything, this is only required for videos.
|
33
36
|
if (!process.env.TWITTER_ACCESS_TOKEN) return
|
34
37
|
|
35
|
- const
|
36
|
-
|
37
|
- {
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+ const proxy = process.env.https_proxy;
|
39
|
+ const res = await fetch(`${API_URL}/1.1/statuses/show/${tweetId}.json?include_entities=true&tweet_mode=extended`, proxy ? {
|
40
|
+ headers: {
|
41
|
+ authorization: `Bearer ${process.env.TWITTER_ACCESS_TOKEN}`
|
42
|
+ },
|
43
|
+ agent: new HttpsProxyAgent(proxy)
|
44
|
+ } : {
|
45
|
+ headers: {
|
46
|
+ authorization: `Bearer ${process.env.TWITTER_ACCESS_TOKEN}`
|
41
47
|
}
|
42
|
- )
|
48
|
+ })
|
43
49
|
|
44
50
|
console.log(
|
45
51
|
'Twitter x-rate-limit-limit:',
|
@@ -68,14 +74,18 @@ export async function fetchTweetWithPoll(tweetId) {
|
|
68
74
|
if (!process.env.TWITTER_ACCESS_TOKEN || !twitterLabsEnabled(expansions))
|
69
75
|
return
|
70
76
|
|
71
|
- const
|
72
|
-
|
73
|
- {
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
+ const proxy = process.env.https_proxy;
|
78
|
+ const res = await fetch(`${API_URL}/labs/1/tweets?format=compact&expansions=${expansions}&ids=${tweetId}`, proxy ? {
|
79
|
+ headers: {
|
80
|
+ authorization: `Bearer ${process.env.TWITTER_ACCESS_TOKEN}`
|
81
|
+ },
|
82
|
+ agent: new HttpsProxyAgent(proxy)
|
83
|
+ } : {
|
84
|
+ headers: {
|
85
|
+ authorization: `Bearer ${process.env.TWITTER_ACCESS_TOKEN}`
|
77
86
|
}
|
78
|
- )
|
87
|
+ })
|
88
|
+
|
79
89
|
|
80
90
|
console.log(
|
81
91
|
'Twitter Labs x-rate-limit-limit:',
|
@@ -99,7 +109,8 @@ export async function fetchTweetWithPoll(tweetId) {
|
|
99
109
|
}
|
100
110
|
|
101
111
|
export async function getEmbeddedTweetHtml(url) {
|
102
|
- const
|
112
|
+ const proxy = process.env.https_proxy;
|
113
|
+ const res = await fetch(`https://publish.twitter.com/oembed?url=${url}`, proxy ? { agent: new HttpsProxyAgent(proxy) } : {})
|
103
114
|
|
104
115
|
if (res.ok) return res.json()
|
105
116
|
if (res.status === 404) return
|
ここまでで対応方法は分かったのですが・・・
react-static-tweetsをforkするのか、自分のソースコード(この場合、itc-blog)に取り込んでしまって良いのか?と対応に迷いが生じ、パッチを当てる方法を見つけて、実践しました。
続きは、npmのpatch-package
の話になります。
↓
npmのpatch-packageを使って、static-tweetにパッチを当てた
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。