HTTP Challenge 攻略與心得

來自 Lidemy 上的小遊戲:Lidemy HTTP Challenge,寫一篇簡短的攻略文與心得。

這份遊戲其實能學到蠻多東西的,很推薦沒玩過的人可以去玩玩看。

第一關

1. 開場

一開始的 token 為 {GOGOGO},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv1?token={GOGOGO}',
(err, res, body) => {
console.log(body)
}

response:

1
2
3
4
5
6
7
8
啊...好久沒有看到年輕人到我這個圖書館了,我叫做 lib,是這個圖書館的管理員
很開心看到有年輕人願意來幫忙,最近圖書館剛換了資訊系統,我都搞不清楚怎麼用了...
這是他們提供給我的文件,我一個字都看不懂,但對你可能會有幫助:https://gist.githu
b.com/aszx87410/3873b3d9cbb28cb6fcbb85bf493b63ba

先把這文件放一旁吧,這個待會才會用到
你叫做什麼名字呢?用 GET 方法跟我說你的 name 叫做什麼吧!
除了 token 以外順便把 name 一起帶上來就可以了

2. 自我介紹

記得把剛剛的 API 網址給記下來,之後會用到。

接著就依照題目說的,帶上 &name=xxx 就可以了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv1?token={GOGOGO}&name=PeaNu',
(err, res, body) => {
console.log(body)
})

response:

1
啊...原來你叫 PeaNu 啊!下一關的 token 是 {HellOWOrld}

第一關就到這邊結束,進到下一關囉。

第二關

1. 開場

利用第一關給的 token:{HellOWOrld},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv2?token={HellOWOrld}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
我前陣子在整理書籍的時候看到了一本我很喜歡的書,可是現在卻怎麼想都想不起來是哪一
本...
我只記得那本書的 id 是兩位數,介於 54~58 之間,你可以幫幫我嗎?
找到是哪一本之後把書的 id 用 GET 傳給我就行了。

2. 輸入正確的 id

接下來要用 &id=54~58 發出 GET 請求,其中會有一個是正確的 id,也就是 56

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv2?token={HellOWOrld}&id=56',
(err, res, body) => {
console.log(body)
})

response:

1
啊!就是這本書,太謝謝你了。下一關的 token 為:{5566NO1}

一樣順利拿到 token,進到下一關!

備註:試著用第一關給的 API,去查詢 id=56 的書,會發現管理員在找的書為「5566 - 認真」,正好對應了 {5566NO1}

第三關

1. 開場

利用第二關給的 token:{5566NO1},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv3?token={5566NO1}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
7
8
真是太感謝你幫我找到這本書了!

剛剛在你找書的時候有一批新的書籍送來了,是這次圖書館根據讀者的推薦買的新書,其中
有一本我特別喜歡,想要優先上架。
書名是《大腦喜歡這樣學》,ISBN 為 9789863594475。

就拜託你了。
新增完之後幫我把書籍的 id 用 GET 告訴我。

2. 新增書籍

這時候就要用到剛剛的 API 了,請閱讀裡面的說明,然後用 POST 把書新增到圖書系統裡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const API_ENDPOINT_V1 = 'https://lidemy-http-challenge.herokuapp.com/api'
request({
method: 'POST',
url: `${API_ENDPOINT_V1}/books`,
headers: {
'Content-type': 'application/x-www-form-urlencoded'
},
form: {
name: '《大腦喜歡這樣學》',
ISBN: 9789863594475
}
},
(err, res, body) => {
console.log(body)
})

我們要用「form 表單」的格式來傳送,所以在 header 加上 'Content-type': 'application/x-www-form-urlencoded'

response:

1
{"message":"新增成功","id":"1989"}

接著把取得的 id 加上去就可以了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv3?token={5566NO1}&id=1989',
(err, res, body) => {
console.log(body)
})

response:

1
這樣子讀者就能趕快看到這本新書了,謝謝!下一關的 token 為 {LEarnHOWtoLeArn}

第四關

1. 開場

利用第三關給的 token:{LEarnHOWtoLeArn},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv4?token={LEarnHOWtoLeArn}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
我翻了一下你之前幫我找的那本書,發現我記錯了...這不是我朝思暮想的那一本。
我之前跟你講的線索好像都是錯的,我記到別本書去了,真是抱歉啊。
我記得我想找的那本書,書名有:「世界」兩字,而且是村上春樹寫的,可以幫我找到書的
id 並傳給我嗎?

2. 找出書本的 id

這邊有兩種方法,一種是在 API 上加上參數,一種是把整筆抓下來後自己找。先示範第一種:

1
2
3
4
5
6
7
8
9
10
11
12
/*
把 API 加上 quest string
這邊要自己把「世界」做 url encode,不然會失敗。
*/
const API_ENDPOINT_V1 = 'https://lidemy-http-challenge.herokuapp.com/api'
request({
method: 'GET',
url: `${API_ENDPOINT_V1}/books?q=%E4%B8%96%E7%95%8C`,
},
(err, res, body) => {
console.log(JSON.parse(body))
})

response:

1
2
3
4
5
6
7
8
9
10
11
[
{ id: 2, name: '當我想你時,全世界都救不了我', author: '肆一', ISBN: '5549173495' },
{ id: 27, name: '從你的全世界路過', author: '張嘉佳', ISBN: '8426216529' },
{ id: 79, name: '世界末日與冷酷異境', author: '村上春樹', ISBN: '9571313408' },
{
id: 90,
name: '文學的40堂公開課:從神話到當代暢銷書,文學如何影響我們、帶領我們理解這個世界',
author: '約翰.薩德蘭',
ISBN: '7978376866'
}
]

第二種,整筆抓下來再搜尋:

1
2
3
4
5
6
7
8
9
10
11
const API_ENDPOINT_V1 = 'https://lidemy-http-challenge.herokuapp.com/api'
request({
method: 'GET',
url: `${API_ENDPOINT_V1}/books`,
},
(err, res, body) => {
const books = JSON.parse(body)
// 搜尋書名為「世界」與作者為「村上春樹」的書
target = books.filter(elem => elem.author === '村上春樹' && elem.name.includes('世界') )
console.log(target)
})

response:

1
[ { id: 79, name: '世界末日與冷酷異境', author: '村上春樹', ISBN: '9571313408' } ]

所以最後拿 id=79 去發請求就行囉:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv4?token={LEarnHOWtoLeArn}&id=79',
(err, res, body) => {
console.log(body)
})

response:

1
沒錯!這次我很確定了,就是這本!下一關的 token 為 {HarukiMurakami}

第四關結束,進入下一關。

備註:token:{HarukiMurakami} 就是村上春樹的名字。

第五關

1. 開場

利用第四關給的 token:{HarukiMurakami},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv5?token={HarukiMurakami}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
昨天有個人匆匆忙忙跑過來說他不小心捐錯書了,想要來問可不可以把書拿回去。
跟他溝通過後,我就把他捐過來的書還他了,所以現在要把這本書從系統裡面刪掉才行。

那本書的 id 是 23,你可以幫我刪掉嗎?

2. 刪除書本

按照題目說的,用 DELETE 請求去把 id 是 23 的書給刪除:

1
2
3
4
5
6
7
8
const API_ENDPOINT_V1 = 'https://lidemy-http-challenge.herokuapp.com/api'
request({
method: 'DELETE',
url: `${API_ENDPOINT_V1}/books/23`,
},
(err, res, body) => {
console.log(body)
})

response:

1
{"message":"\n咦...是刪掉了沒錯,但總覺得哪裡怪怪的,算了,先這樣吧!下一關的 token 為 {CHICKENCUTLET}\n"}

第五關結束,進入下一關。

備註:被刪除的書 其實是雞排妹的寫真集,所以 token 才叫做 {CHICKENCUTLET}(雞排)。

第六關

1. 開場

利用第五關給的 token:{CHICKENCUTLET},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv6?token={CHICKENCUTLET}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
7
8
9
10
11
我終於知道上次哪裡怪怪的了!

照理來說要進入系統應該要先登入才對,怎麼沒有登入就可以新增刪除...
這太奇怪了,我已經回報給那邊的工程師了,他們給了我一份新的文件:https://gist.github.com/aszx87410/1e5e510
5c1c35197f55c485a88b0328a。

這邊是帳號密碼,你先登入試試看吧,可以呼叫一個 /me 的 endpoint,裡面會給你一個 email。
把 email 放在 query string 上面帶過來,我看看是不是對的。

帳號:admin
密碼:admin123

2. 取得使用者資訊

這邊先參考新的 API 文件說明,裡面提到的驗證其實就是 HTTP 中的「Basic Authentication」,詳細可以參考 這篇文章

所以這邊得依照格式把 admin:admin123 做 「base64」編碼,再帶上 Authorization 的 header:

1
2
3
4
5
6
7
8
9
10
const API_ENDPOINT_V2 = 'https://lidemy-http-challenge.herokuapp.com/api/v2'
// 以 base64 編碼
const encode = Buffer.from('admin:admin123').toString('base64')
request({
url: `${API_ENDPOINT_V2}/me`,
// 帶上驗證 header
headers: {
'Authorization': `Basic ${encode}`
}
}, (err, res, body) => console.log(body))

備註:關於 Buffer 的用法可以參考 Node 中的 buffer 模組

response:

1
{"username":"admin","email":"lib@lidemy.com"}

最後把 email 帶到參數裡這關就 pass 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv6?token={CHICKENCUTLET}&email=lib@lidemy.com',
(err, res, body) => {
console.log(body)
})

response:

1
對對對,就是這個,這樣才對嘛!下一關的 token 為 {SECurityIsImPORTant}

備註:{SECurityIsImPORTant} = 安全性很重要。

第七關

1. 開場

利用第六關給的 token:{SECurityIsImPORTant},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv7?token={SECurityIsImPORTant}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
7
那邊的工程師說系統整個修復完成了,剛好昨天我們發現有一本書被偷走了...
這本書我們已經買第五次了,每次都被偷走,看來這本書很熱門啊。
我們要把這本書從系統裡面刪掉,就拜託你了。

對了!記得要用新的系統喔,舊的已經完全廢棄不用了。

書的 id 是 89

2. 刪除書本

就是用 DELETE 去把書本刪掉而已:

1
2
3
4
5
6
7
8
const API_ENDPOINT_V2 = 'https://lidemy-http-challenge.herokuapp.com/api/v2'
request({
method: 'DELETE',
url: `${API_ENDPOINT_V2}/books/89`,
headers: {
'Authorization': 'Basic YWRtaW46YWRtaW4xMjM='
}
}, (err, res, body) => console.log(body))
1
{"message":"\n希望下一次進這本書的時候不會再被偷走了。下一關的 token 為 {HsifnAerok}\n"}

順利進到下一關。

備註:這本被偷走的書其實是「跟著月亮走:韓國瑜的夜襲精神與奮進人生」,所以把 {HsifnAerok} 倒過來就會發現是 {koreAnfisH}(韓國魚)

1
{"id":89,"name":"跟著月亮走:韓國瑜的夜襲精神與奮進人生","author":"韓國瑜","ISBN":"9789571376882"}

第八關

1. 開場

利用第七關給的 token:{HsifnAerok},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv8?token={HsifnAerok}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
我昨天在整理書籍的時候發現有一本書的 ISBN 編號跟系統內的對不上,仔細看了一下發現我當時輸入系統時 key 錯了

哎呀,人老了就是這樣,老是會看錯。

那本書的名字裡面有個「我」,作者的名字是四個字,key 錯的 ISBN 最後一碼為 7,只要把最後一碼改成 3 就行了。
對了!記得要用新的系統喔,舊的已經完全廢棄不用了。

2. 找出填錯資訊的書本

這邊一樣可以用 API 參數來找,或者是整筆抓下來再搜尋。先示範第一種:

1
2
3
4
5
6
7
8
9
const API_ENDPOINT_V2 = 'https://lidemy-http-challenge.herokuapp.com/api/v2'
const encode = Buffer.from('admin:admin123').toString('base64')
request({
// 搜尋「我」,記得一樣要做 url encode
url: `${API_ENDPOINT_V2}/books?q=%E6%88%91`,
headers: {
'Authorization': `Basic ${encode}`
}
}, (err, res, body) => console.log(JSON.parse(body)))

response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[
{ id: 2, name: '當我想你時,全世界都救不了我', author: '肆一', ISBN: '5549173495' },
{ id: 3, name: '我殺的人與殺我的人', author: '東山彰良', ISBN: '9262228645' },
{ id: 7, name: '你已走遠,我還在練習道別', author: '渺渺', ISBN: '3722233689' },
{ id: 9, name: '你是我最熟悉的陌生人', author: 'Middle', ISBN: '9765734253' },
{ id: 22, name: '我輩中人:寫給中年人的情書', author: '張曼娟', ISBN: '7241428897' },
{ id: 38, name: '我和我追逐的垃圾車', author: '謝子凡', ISBN: '7797349452' },
{ id: 57, name: '我的櫻花戀人', author: '宇山佳佑', ISBN: '2947749939' },
{ id: 60, name: '你走慢了我的時間', author: '張西', ISBN: '8811544334' },
{ id: 66, name: '我是許涼涼', author: '李維菁', ISBN: '8389193464' },
{
id: 72,
name: '日日好日:茶道教我的幸福15味【電影書腰版】',
author: '森下典子',
ISBN: '9981835427'
},
{
id: 90,
name: '文學的40堂公開課:從神話到當代暢銷書,文學如何影響我們、帶領我們理解這個世界',
author: '約翰.薩德蘭',
ISBN: '7978376866'
},
{
id: 95,
name: '我想吃掉你的胰臟【電影珍藏版】',
author: '住野夜',
ISBN: '2615985356'
},
{
id: 100,
name: '慢情書:我們會在更好的地方相遇嗎?',
author: '林達陽',
ISBN: '7418527246'
}
]

(有蠻多筆符合的,所以抓下來後還是要再搜尋一下比較清楚)

第二種,整筆抓下來搜尋:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const API_ENDPOINT_V2 = 'https://lidemy-http-challenge.herokuapp.com/api/v2'
request({
method: 'GET',
url: `${API_ENDPOINT_V2}/books`,
headers: {
'Authorization': 'Basic YWRtaW46YWRtaW4xMjM='
}
}, (err, res, body) => {
// 所有書籍
const books = JSON.parse(body)
// 找出包含「我」跟「四個字的作者」跟「以 7 結尾的 ISBN 碼」
const target =
books.filter(elem =>
elem.name.includes('我') &&
elem.author.length === 4 &&
elem.ISBN.endsWith('7'))
// 結果
console.log(target)
})

response:

1
2
3
4
5
6
7
8
[
{
id: 72,
name: '日日好日:茶道教我的幸福15味【電影書腰版】',
author: '森下典子',
ISBN: '9981835427'
}
]

3. 修正書本資訊

知道是哪一本書之後,用 PATCH 去修改 ISBN 的值就好囉:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const API_ENDPOINT_V2 = 'https://lidemy-http-challenge.herokuapp.com/api/v2'
request({
method: 'PATCH',
url: `${API_ENDPOINT_V2}/books/72`,
headers: {
'Authorization': 'Basic YWRtaW46YWRtaW4xMjM=',
'Content-type': 'application/x-www-form-urlencoded'
},
form: {
// 把 7 改成 3
ISBN: '9981835423'
}
}, (err, res, body) => {
console.log(body)
})

response:

1
{"message":"\n希望之後他們能引進語音輸入系統,我就只要講講話就好。下一關的 token 為 {NeuN}\n"}

第八關結束,進到下一關。

備註:{NeuN} 是德文的 9

第九關

1. 開場

利用第八關給的 token:{NeuN},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv9?token={NeuN}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
7
8
API 文件裡面有個獲取系統資訊的 endpoint 你記得嗎?
工程師跟我說這個網址不太一樣,用一般的方法是沒辦法成功拿到回傳值的。

想要存取的話要符合兩個條件:
1. 帶上一個 X-Library-Number 的 header,我們圖書館的編號是 20
2. 伺服器會用 user agent 檢查是否是從 IE6 送出的 Request,不是的話會擋掉

順利拿到系統資訊之後應該會有個叫做 version 的欄位,把裡面的值放在 query string 給我吧。

2. 取得系統資訊

這邊要多帶上兩個 header,分別是 X-Library-NumberUser-Agent

X-Library-Number 這種以 X 為前綴的代表是自定義的 header,不在 HTTP 規範中,不加也沒關係,但這裡的伺服器需要識別,所以得加上這個欄位。

User-Agent 是用來紀錄用戶端的「瀏覽器資訊」跟「作業系統」等等的資訊,所以這邊要把它改成是 IE6 的內容。直接上網查就可以找到了:Changing User Agent to IE6 or lower for curl call

原始碼:

1
2
3
4
5
6
7
8
9
10
11
12
const API_ENDPOINT_V2 = 'https://lidemy-http-challenge.herokuapp.com/api/v2'
request({
method: 'GET',
url: `${API_ENDPOINT_V2}/sys_info`,
headers: {
'Authorization': 'Basic YWRtaW46YWRtaW4xMjM=',
'X-Library-Number': '20',
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)'
}
}, (err, res, body) => {
console.log(body)
})

response:

1
{"message":"success","version":"1A4938Jl7","owner":"lib","createdAt":"121290329301"}

這裡補充一下,「改 User-Agent 的內容」是在不使用「瀏覽器」的前提下才可以做到的事情。如果你是透過瀏覽器來發 request 的話是沒辦法改的,詳細可以參考 從遊戲來認識 CORS 與瀏覽器的限制

接著把 1A4938Jl7 帶到參數裡就解決第九關了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv9?token={NeuN}&version=1A4938Jl7',
(err, res, body) => {
console.log(body)
})

response:

1
限制這麼多都被你突破了,真有你的。要不要考慮以後來我們圖書館當工程師啊?下一關的 token 為 {duZDsG3tvoA}

備註:{duZDsG3tvoA}半島鐵盒 的 youtube 編號。

第十關

1. 開場

利用第九關給的 token:{duZDsG3tvoA},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv10?token={duZDsG3tvoA}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
7
8
時間過得真快啊,今天是你在這邊幫忙的最後一天了。

我們來玩個遊戲吧?你有玩過猜數字嗎?

出題者會出一個四位數不重複的數字,例如說 9487。
你如果猜 9876,我會跟你說 1A2B,1A 代表 9 位置對數字也對,2B 代表 8 跟 7 你猜對了但位置錯了。

開始吧,把你要猜的數字放在 query string 用 num 當作 key 傳給我。

2. 猜出正確的數字

就是根據題目的敘述來猜就行了,正確的數字為 9613

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv10?token={duZDsG3tvoA}&num=9613', 
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
7
8
9
10
很開心在這裡的時光能有你一起陪伴,讓我的生活不再那麼一成不變,很開心認識你,下次有機會再一起玩吧!

The End,恭喜破關!

author: huli@lidemy.com
https://www.facebook.com/lidemytw/

附註:
原本遊戲只規劃到這邊,第十關就是最後一關。
後來我有加了一些新關卡但難度較高,如果你還想挑戰看看,下一關的 token 為 {IhateCORS}

第十一關

1. 開場

利用第十關給的 token:{IhateCORS},發一個 GET 請求就 OK 了:

1
2
3
4
5
const request = require('request')
request('https://lidemy-http-challenge.herokuapp.com/lv11?token={IhateCORS}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
嘿!很開心看到你願意回來繼續幫忙,這次我們接到一個新的任務,要跟在菲律賓的一個中
文圖書館資訊系統做串連
這邊是他們的 API 文件,你之後一定會用到:https://gist.github.com/aszx87410/0b0d3
cabf32c4e44084fadf5180d0cf4。

現在就讓我們先跟他們打個招呼吧,只是我記得他們的 API 好像會限制一些東西就是了...

記得把 API 網址存起來。

2. 打招呼

接著就按照 API 文件的說明,發一個「打招呼」的 request:

1
2
3
4
5
const API_ENDPOINT = 'https://lidemy-http-challenge.herokuapp.com/api/v3'
request(`${API_ENDPOINT}/hello`,
(err, res, body) => {
console.log(body)
})

response:

1
您的 origin 不被允許存取此資源,請確認您是從 lidemy.com 送出 request。

這時候會跟你說你的位置不允許,所以我們又得再加一些設定了。還記得 第九關 的套路嗎?

這裡用一樣的做法就可以了:

1
2
3
4
5
6
7
8
9
10
11
const API_ENDPOINT = 'https://lidemy-http-challenge.herokuapp.com/api/v3'
request({
url: `${API_ENDPOINT}/hello`,
headers: {
'Origin': 'https://lidemy.com'
}
},
(err, res, body) => {
console.log(res.request.headers)
console.log(body)
})

response:

1
Hello! 下一關的 token 為 {r3d1r3c7}

備註:再次提醒,「改 Origin 的」和「改 User-Agent」一樣都是建立在不使用「瀏覽器」的前提下才能做到的事。

第十二關

1. 開場

利用第十一關給的 token:{r3d1r3c7},發一個 GET 請求就 OK 了:

1
2
3
4
5
const request = require('request')
request('https://lidemy-http-challenge.herokuapp.com/lv12?token={r3d1r3c7}',
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
打完招呼之後我們要開始送一些書過去了,不過其實運送沒有你想像中的簡單,不是單純的 A 到 B 而

而是像轉機那樣,A 到 C,C 才到 B,中間會經過一些轉運點才會到達目的地...算了,我跟你說那麼
多幹嘛

現在請你幫我把運送要用的 token 給拿回來吧,要有這個 token 我們才能繼續往下一步走

2. 取得運送 Token

這時候一樣按照 API 文件來發出「獲取運送 token」的 request:

1
2
3
4
5
6
7
const API_ENDPOINT = 'https://lidemy-http-challenge.herokuapp.com/api/v3'
request({
url: `${API_ENDPOINT}/deliver_token`,
},
(err, res, body) => {
console.log(body)
})

response:

1
我已經把運送要用的 token 給你囉,請你仔細找找

當時我的內心充滿了問號,還以為是在玩什麼反白梗之類的,但很遺憾:不要瞎掰好嗎。

所以就來調查一下吧,我們先試著從 headers 來看能不能找出一點蛛絲軌跡:

1
2
3
4
5
6
7
8
const API_ENDPOINT = 'https://lidemy-http-challenge.herokuapp.com/api/v3'
request({
url: `${API_ENDPOINT}/deliver_token`,
},
(err, res, body) => {
// 檢查 response headers
console.log(res.headers)
})

response:

1
2
3
4
5
6
7
8
9
{
server: 'Cowboy',
connection: 'close',
'x-powered-by': 'Express',
'content-type': 'text/plain; charset=utf-8',
date: 'Sat, 15 Jan 2022 07:05:12 GMT',
'content-length': '66',
via: '1.1 vegur'
}

嗯…沒有什麼有用的資訊,所以接著改看請求的 headers 吧:

1
2
3
4
5
6
7
8
const API_ENDPOINT = 'https://lidemy-http-challenge.herokuapp.com/api/v3'
request({
url: `${API_ENDPOINT}/deliver_token`,
},
(err, res, body) => {
// 檢查 request headers
console.log(res.request.headers)
})

response:

1
2
3
{
referer: 'https://lidemy-http-challenge.herokuapp.com/api/v3/stopover'
}

哦哦!原來是被「轉址」了阿,難怪一開始會說:

像轉機那樣,A 到 C,C 才到 B,中間會經過一些轉運點才會到達目的地…

這表示我們想要的資訊說不定就放在被轉址「之前的位置」,只要能夠找到原本的位置就行了。

這邊有兩種作法,我先講比較簡單的:利用瀏覽器

  1. 在網址輸入 API 的網址: https://lidemy-http-challenge.herokuapp.com/api/v3/deliver_token

redirect-01

這時候會看到直接被重新導向到 /deliver_token_result 的位置

  1. 接下來請打開 F12,並切換到「Network」的欄位,重新輸入正確的 API 網址送出。最後應該就能看到以下資訊:

redirect-02

備註:狀態碼的意思可以參考我寫的 HTTP 懶人包

所以我們一共經過了三個地方:deliver_token => stopover => deliver_token_result

而 token 確實就放在 deliver_token 的 response headers 中,只是因為剛剛被重新導向到 deliver_token_result 所以才看不到。

有興趣的人可以自己把每個 request 的內容打開來看,這邊我就不貼圖片了,因為我懶。順道一提「stopover」是「中途停留」的意思。

接下來說明第二種作法:在 Node.js 中阻止重新導向

因為我是用 request 來發送請求的,所以要參考它的官方文件說明

followRedirect - follow HTTP 3xx responses as redirects (default: true). This property can also be implemented as function which gets response object as a single argument and should return true if redirects should continue or false otherwise.

簡單來說就是你可以設定一個 followRedirect 的「option」,只要讓這個 function 回傳 false 就不會被自動轉址了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const API_ENDPOINT = 'https://lidemy-http-challenge.herokuapp.com/api/v3'
request({
url: `${API_ENDPOINT}/deliver_token`,
// 阻止重新導向
followRedirect: function() {
return false
}
},
(err, res, body) => {
// 順便檢查一下狀態碼
console.log(res.statusCode)
// response headers
console.log(res.headers)
})

response:

1
2
3
4
5
6
7
8
9
10
11
12
13
302
{
server: 'Cowboy',
connection: 'close',
'x-powered-by': 'Express',
'content-type': 'text/plain; charset=utf-8',
'x-lv13-token': '{qspyz}',
location: '/api/v3/stopover',
vary: 'Accept',
'content-length': '38',
date: 'Sat, 15 Jan 2022 07:41:04 GMT',
via: '1.1 vegur'
}

就跟剛剛在瀏覽器看到的一樣,得到 302 重新導向的狀態碼,導向的位置也是 stopover

總而言之,看你喜歡哪種方法就用哪種,最後就順利拿到 token 囉!

第十三關

1. 開場

利用第十二關給的 token:{qspyz},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv13?token={qspyz}', 
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
太好了!自從你上次把運送用的 token 拿回來以後,我們就密切地與菲律賓在交換書籍
可是最近碰到了一些小問題,不知道為什麼有時候會傳送失敗
我跟他們反映過後,他們叫我們自己去拿 log 來看,你可以幫我去看看嗎?
從系統日誌裡面應該可以找到一些端倪

2. 查看系統日誌

一樣先按照 API 文件來發一個「獲取系統日誌」的請求:

1
2
3
4
5
6
7
const API_ENDPOINT = 'https://lidemy-http-challenge.herokuapp.com/api/v3'
request({
url: `${API_ENDPOINT}/logs`,
},
(err, res, body) => {
console.log(body)
})

response:

1
此 request 不是來自菲律賓,禁止存取系統資訊。

這代表 server 會檢查使用者的 IP 地址,所以我們可以透過「proxy(代理伺服器)」來發送請求。

所以只要找到一個菲律賓的 proxy 來做設定就可以了。不過這邊我弄了老半天就是不行,每當我設定好 proxy 的時候網路就會掛掉,我也不確定問題出在哪。如果有人知道原因的話請務跟告訴我一下QQ。

因此這裡改用另外一種解法:設定 X-Forwarded-For

簡單來說 server 可以透過這個 header 來判斷使用者的 IP 地址,因此只要把 IP 地址設成菲律賓的位置就行了:

1
2
3
4
5
6
7
8
9
10
const API_ENDPOINT = 'https://lidemy-http-challenge.herokuapp.com/api/v3'
request({
url: `${API_ENDPOINT}/logs`,
headers: {
'X-Forwarded-For': '49.145.251.55'
}
},
(err, res, body) => {
console.log(body)
})

response:

1
2
3
[
{ logType: 'token', value: '{SEOisHard}' }
]

千辛萬苦地進到下一關(汗

第十四關

1. 開場

利用第十三關給的 token:{SEOisHard},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv14?token={SEOisHard}', 
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
7
8
跟那邊的溝通差不多都搞定了,真是太謝謝你了,關於這方面沒什麼問題了!
不過我老大昨天給了我一個任務,他希望我去研究那邊的首頁內容到底是怎麼做的
為什麼用 Google 一搜尋關鍵字就可以排在第一頁,真是太不合理了

他們的網站明明就什麼都沒有,怎麼會排在那麼前面?
難道說他們偷偷動了一些手腳?讓 Google 搜尋引擎看到的內容跟我們看到的不一樣?

算了,還是不要瞎猜好了,你幫我們研究一下吧!

2. 檢查首頁內容

這裡顯然要檢查一下首頁的部分,所以一樣先按照 API 文件來發一個「獲取首頁內容」的請求:

1
2
3
4
5
6
request({
url: `${API_ENDPOINT}/index`,
},
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
<html>
<h1>Library</h1>
<p>Hello, here is the website for lidemy library</p>
</html>

果然看不出個所以然。回想一下剛剛的敘述「難道說他們偷偷動了一些手腳?讓 Google 搜尋引擎 看到的內容跟我們看到的不一樣?」

所以換句話說要讓 server「以為」我們是 Google 搜尋引擎,但該怎麼做呢?想想之前的套路,既然都有辦法讓 server 以為是 IE6 了,那搜尋引擎應該也是同樣的道理。

所以試著搜尋:「search engine user agent list」

你就能找到這個 網站

備註:如果想知道更多細節,這個網站 寫的還蠻簡單易懂的。

接下來應該就知道該怎麼做了吧?

1
2
3
4
5
6
7
8
9
request({
url: `${API_ENDPOINT}/index`,
headers: {
'User-Agent': 'Googlebot/2.1 (+http://www.googlebot.com/bot.html)'
}
},
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
<html>
<h1>I Love Google</h1>
<p>Google please rank our website higher, PLEASE!</p>
<!-- token for lv15:{ILOVELIdemy!!!} -->
</html>

接下來終於來到最後一關囉…

第十五關

利用第十四關給的 token:{ILOVELIdemy!!!},發一個 GET 請求就 OK 了:

1
2
3
4
request('https://lidemy-http-challenge.herokuapp.com/lv15?token={ILOVELIdemy!!!}', 
(err, res, body) => {
console.log(body)
})

response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
還真的是我猜的那樣...不過還是要謝謝你幫我們完成這麼多任務!
今天是我在這職位的最後一天啦,之後我要升官了,應該就不用處理這麼多小事情了
這段期間感謝你的幫忙,我們以後有緣再相見啦!

The End,恭喜破關!
這次是真的破關了,這是最後一關,感謝你願意參與這個遊戲
也希望這遊戲是有趣的,希望你在玩的時候有學到東西
也歡迎把這個遊戲分享給親朋好友們
感謝!

我開了一個 gist,大家可以在這邊隨意留言,或是講一下破關感言
https://gist.github.com/aszx87410/1dbde92876ba253a45654988ca829ebb

最後,感謝所有幫我測試的朋友們

Author: huli@lidemy.com
https://www.facebook.com/lidemytw/

完結撒花啦~

心得

好不容易破關啦!第十五關後的難度真的明顯提升,尤其是第十三關真的讓卡了很久,明明就按照網路上的教學去設定 proxy 了,但就是沒有辦法,最後只好不甘心的上網找正確解答,才發現了 X-Forwarded-For 的解法。

在這個遊戲裡讓我感觸最深的地方還是「執行環境」這件事吧,例如說改「Origin」和「User-Agent」真的讓我大開眼界,這跟我熟悉的「瀏覽器」完全是兩個世界,原來脫離了瀏覽器可以做到的事這麼多,這是我覺得很有趣的地方。

另外還有一點是原來 server 還能從 User-Agent 來判斷是不是爬蟲,甚至還有專屬爬蟲的 User-Agent,原本單純的我完全不知道有這樣神奇的事情存在,很慶幸我能從這個遊戲裡學到這些。

最後很謝謝 Huli 為大家做了這個遊戲,這是一個很有意思也能學到很多東西的遊戲,這個遊戲除了讓我更了解網路世界外,也更了解在前後端這兩個角色在自己的世界裡實際上都做了些什麼。

從遊戲來認識 CORS 與瀏覽器的限制 Node 中的 buffer 模組
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×