0%

相信用過 Visual Studio 的應該都注意過編譯時可以選擇 Any CPU、x86、x64 (x86、x64 需要編輯組態檔才會看的到)

/images/20190212/1.png

這次踩到問題是,明明程式用 Any CPU 來編譯,但在 64 bit OS 上卻會用 32 bit Process 來執行

/images/20190212/2.png

原來是因為專案建置的屬性勾到了 建議使用32位元的選項導致

/images/20190212/3.png

解決的同時也引起了對於編譯成 x86、x64、Any CPU 有什麼差別的好奇心,找了文章後整理如下


差異


在 32 bit OS 上執行

Any CPU : 會用 32 bit Process 來執行,可以載入 Any CPU 和 x86 方式編譯的組件,但如果載入 x64 編譯的組件將會引發 BadImageFormatException

Any CPU (勾選建議使用32位元) : 運作方式同上

x86 : 運作方式同上

x64 : 引發 BadImageFormatException


在 64 bit OS 上執行

Any CPU : 會用 64 bit Process 來執行,可以載入 Any CPU 和 x64 方式編譯的組件,但如果載入 x86 編譯的組件將會引發 BadImageFormatException

Any CPU (勾選建議使用32位元) : 會用 32 bit Process 來執行,可以載入 Any CPU 和 x86 方式編譯的組件,但如果載入 x64 編譯的組件將會引發 BadImageFormatException

x86 : 運作方式同 Any CPU (勾選建議使用32位元)

x64 : 運作方式同 Any CPU


Machine Config

依據執行的 Process 位元決定

32 bit Process 吃的 Machine Config 位置 : C:\Windows\Microsoft.NET\ Framework \v4.0.30319\Config

64 bit Process 吃的 Machine Config 位置 : C:\Windows\Microsoft.NET\ Framework64 \v4.0.30319\Config


怎麼查是用哪種方式編譯的?

可以透過 CorFlags.exe 的工具來查詢,該程式放在 C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools 路徑底下,設定為環境變數後就可以用 command 來檢視 PE

1
$ corflags yourPE.exe

PE : Process Executables (EXEs and DLLs)

/images/20190212/4.png

意義如下

/images/20190212/5.png

(圖片節錄至 : https://stackoverflow.com/questions/18608785/how-to-interpret-the-corflags-flags/23614024#23614024)

什麼是 Yield

Yield 就是 .Net 中用來實作 iterator(迭代器) 設計模式的語法糖,雖然很早就知道這東西,但一直都不太知道怎麼用,剛好最近同事在翻寫寄送大量信件的程式有用到就跟他請教了一下。

以前的做法

將收件人從 DB 撈出來後 ,依據每封信的 Template 去置換內容,再批次送去給寄信服務執行

問題

如果這批收件人很多,把整批的信件都置換完 Template 再送出,會導致系統吃掉大量的記憶。當然最簡單的解決方法就是用迴圈組完一筆就送出一次,但其實用 Yield Return 可以更優雅的解決這個問題

範例

同事提供了簡單的範例來理解這件事情

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
static void Main(string[] args)
{
foreach (var mail in GetData())
{
Console.WriteLine($"- Received: {mail.Title}");
}
}

static List<Mail> GetData()
{
var mailList = new List<Mail>();
int buffer_size = 1024 * 1024 * 4; // 4MB
Random _rnd = new Random();

for (int index = 0; index < 1024; index++)
{
var _buffer = new byte[buffer_size];
_rnd.NextBytes(_buffer);

mailList.Add(new Mail()
{
Title = $"buffer[{index}], {buffer_size / 1024 / 1024} MB",
Buffer = _buffer,
});
Task.Delay(50).Wait();
}

return mailList;
}

public class Mail
{
public string Title { get; set; }
public byte[] Buffer { get; set; }
}

GetData() 這是模擬處理信件的地方,每封信件假設 4MB (到底是寄啥…),整批處理完後回傳 mailList

如果把這段 Code 放到 LINQPad 執行,會發現記憶體一直往上飆,因為會全部都做完放到 List 再回傳並寄信

/images/20181221/1.gif

Yield Return 版本

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
static void Main(string[] args)
{
foreach (var mail in GetData())
{
Console.WriteLine($"- Received: {mail.Title}");
}
}

static IEnumerable<Mail> GetData()
{
int buffer_size = 1024 * 1024 * 4; // 4MB
Random _rnd = new Random();

for (int index = 0; index < 1024; index++)
{
var _buffer = new byte[buffer_size];
_rnd.NextBytes(_buffer);

yield return new Mail()
{
Title = $"buffer[{index}], {buffer_size / 1024 / 1024} MB",
Buffer = _buffer,
};
Task.Delay(50).Wait();
}

yield break;
}

public class Mail
{
public string Title { get; set; }
public byte[] Buffer { get; set; }
}

觀察記憶體會發現非常的平穩,因為逐筆處理完就被消滅掉了

/images/20181221/2.gif

小結

至於如果不用語法糖 Yield 該如何實作迭代器,可參考安德魯的部落格裡面介紹非常詳細,希望這個方法以後可以更融會貫通的套在專案中使用

情境

假設有一項任務,執行時會做三件事情,而這三件事情彼此並沒有相依關係,傳統程式寫法通常都是 1 > 2 > 3 依序呼叫下去做,但這樣有一個問題就是,假設 1 這個任務要執行特別久,那 2、3 都得等 1 做完才能執行到,整體來說效率並沒有最佳化。

/images/20181203/1.png

跑一次需要 2.012 秒完成,如果需要重複 10 次 、100 次,那所需時間會相當可觀。

非同步版本

改成非同步版本後,它執行順序就不會被第一步給拖累,三個工作是併行處理

/images/20181203/2.png

但這會有一個問題,如果程式是 Run 在 Console Application 中,因為沒有等待非同步的程式都執行完,就已經跑到 Done ,而導致 Console Application 被關掉,其相應的 Process 也會被停掉,導致有些工作沒有執行完,所以必須要確保所有程式都執行完才關閉的機制在。

/images/20181203/3.png

參考文章

  1. MSDN : 以工作為基礎的非同步程式設計

日文中的疑問子句

普通型 + かどうか + 子句

  1. このカメラは日本で買ったかどうか、わからない
    這個相機是不是在日本買的,我不知道

  2. 先生が上手に歌えるかどうか、わからない
    老師唱歌拿不拿手,我不知道

(前面出現疑問詞)+普通型 + か + 子句

  1. このカメラは誰のか、わからない
    這個相機是誰的,我並不知道

  2. いつ日本へ行くか、知っていますか
    何時要去日本,你知道嗎?

情境

寫單元測試的時候編譯都沒問題,但在執行時一直報錯 System.MissingMethodException 的錯誤,查了一下發現跟 System.Net.Http 有關

System.MissingMethodException with message

“Method not found: ‘System.Web.Http.Results.ResponseMessageResult

System.Web.Http.ApiController.ResponseMessage(System.Net.Http.HttpResponseMessage)”

檢查測試專案與受測專案的 System.Net.Http Nuget套件皆為同一個版本

/images/20181114/1.png

但意外發現兩個專案的 System.Net.Http 參考卻為不同位置

/images/20181114/2.png

/images/20181114/3.png

照理來說應該都要參考到專案內的 packages 底下才對,檢查 .csproj 裡面的 Reference 並沒有寫錯

1
2
3
<Reference Include="System.Net.Http, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> 		  
<HintPath>..\..\packages\System.Net.Http.4.3.2\lib\net46\System.Net.Http.dll</HintPath>
</Reference>

在網路上也搜尋到相關文章討論似乎是 Visual Studio Bug 導致

/images/20181114/4.png

這位仁兄也碰到一樣問題

  1. 他開啟了一個新專案

  2. Nuget 安裝 System.Net.Http v4.3.3

  3. 建置時 System.Net.Http 參考都還是來自於 Package 資料夾底下

  4. Nuget 安裝 System.Collections.Immutable v1.4.0

  5. 建置時 System.Net.Http 參考就跑到 C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\Microsoft\Microsoft.NET.Build.Extensions\net461\lib\System.Net.Http.dll 底下

解決方法

檢查 Microsoft.NET.Build.Extensions 資料夾底下的 dll 版本為 4.2.0.0 ,而 Nuget System.Net.Http 4.3.2 的 dll 版本則為 4.1.1.1 ,這也是間接導致發生此錯誤的原因。

但 Reference HintPath也沒寫錯,VS 裡面也沒辦法調整 dll 位置 , 最後索性直接刪除 Microsoft.NET.Build.Extensions 資料夾底下的 System.Net.Http ,重新建置後 dll 就重新指向 Package了,此問題得到解決

世界又再一次恢復了和平,!@#$%

參考文章

  1. MIssing method in System.Web.Http.ApiController.get_Request()
  2. System.Net.Http package 4.3.2 - redirect to 4.2.0.0, assembly loading failure

這篇是要記錄關於一些時間的用法

On time

『片語』 準時; 準點

例句 :

The train is always on time.

這班火車總是非常準時

Ruth never arrives office on time.

魯斯總是沒有準時到公司

In time

『片語』 即時;最後

例句 :

I arrived at the office in time for the meeting.

我及時趕上了公司的會議

I finished the test in time

我在最後一秒完成考試

Time off

『片語』 請假;休假

例句 :

I have some time off in July, so I’m going to the beach.

我在7月有一些假日,所以我應該會去海邊

I can’t take time off to go to Iceland

我不能請假去冰島

Time out

『片語』 暫停;休息時間

例句 :

I need to take some time out( time off ). I’ve been very stressed at work recently.

我需要一些休息時間.最近工作壓力太大

About time

『片語』 是時候了

例句 :

It’s about time to go to Iceland.

是時候該去冰島了

Spend time

花時間

例句 :

I’m going to spend some time with my grandmother at the weekend.

我這週末會花些時間陪祖母

Kill time

殺時間

例句:

I love to kill time in the afternoon by reading a good book.

我喜歡花整個下午的時間讀本好書

Waste time

浪費時間

例句 :

I waste so much time using the internet on my phone.

我浪費很多時間在用手機上網

Pressed for time

『片語』 時間緊迫

例句 :

Sorry, I can’t talk at the moment, I’m a bit pressed for time.

抱歉, 我現在無法說話, 因為我時間有點緊迫

Save time

『片語』 節省時間

例句 :

You can save time by using an electronic dictionary.

用電子辭典可以替你省下很多時間

幾天前聽強者我同事上課提到寫部落格跟筆記的重要,想一想的確自己這樣斷斷續續也寫了 5~6 年左右,漸漸也陷入了一種迷失之中

這篇的內容隨便google就有答案了還需要寫嗎?

但忘了最初寫部落格的初衷不是為了給誰看,而是想記錄遇到的難題跟解決的方法,幫助自己能快速查找並系統的學習解。我的部落格其實也沒啥人看,不知道有啥好包袱的

重點一直都不在有沒有人想看,而是我從中又學到了什麼,所以打算來寫幾篇會長期更新的學習筆記,可能包含 AWS、英文、日文..等。希望幾年後,這些會成為自己的能量與成長的基石。

Down the line

未來

從字面解釋就是「沿著線往下」,有過段時間未來的意思,

例句 :

Pick one OS for your production set-up. No need to get master of every OS out there. It would make your job diffcult down the line.

選一種 OS 做為你生產的設置,你不需要精通每一種 OS ,這只會讓你之後的工作變得困難。

If you don’t learn English now , you will get many problems down the line.

如果你現在不學習英文, 那你將來會碰到許多問題的

徹底、始終

例句 :

Errors are to be found down the line.

錯誤總有一天會被找到的

If something should be yours , that will be yours down the line.

屬於你的,終究會是你的

2018-10-25 更新 :DotNet Core 版本已經找出解決方案了,更新在最下面

注意 !!

這篇的結論是 DotNet Core + Selenium + Chrome headless + AWS Lambda 執行是失敗的,所以如果不想看失敗案例的可以直接跳過了,寫下來是因為過程踩了太多雷想記錄一下 ,如果想要簡單一點的作法,建議採用 NodeJs + Chrome Headless + AWS Lambda 的解決方案,幾乎不用什麼調整即可使用。

NodeJs 版本參考: adieuadieu/serverless-chrome

情境

近期公司希望將訂單快照從 PhantomJS 改成 Chrome Headless 來實作,然後放到 AWS Lambda 上透過 AWS SQS 來觸發,達成 Serverless 的架構。

隨著 Chrome Headless 的推出,PhantomJS 也宣布停止更新了,想當年也跟它奮戰許久,又一滴時代的眼淚阿,而要能在 Lambda 上執行,則必須是能在 Linux 上執行的程式,所以 .Net Core 成為這次的選擇,為了不要每次改程式都要佈署到 Lambda 上才能測試(跑一次大概5分鐘),所以透過 Docker 模擬 AWS Lambda 的環境直接在本機測試。

環境

安裝

DotNet Core 2.1 : 下載
Docker for Windows : 下載

實作

因為大量使用到 AWS SDK,查找官方 API 文件會更清楚些,所以本篇不著重在程式內容本身,只會帶到自己覺得關鍵的地方。

建立 .Net Core 類別庫專案

請選擇 .Net Core > 類別庫 (.Net Core)

/images/20181011/1.png

安裝 AWS 相關套件

/images/20181011/2.png

實作 SQSHandler

因為是接收 SQS 來的命令,所以我建立了一支類別 SQSHandler ,並且寫了一個名為 Snapshot 的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// 執行快照
/// </summary>
/// <param name="sqsEvent">The SQS event.</param>
/// <returns>執行結果</returns>
public async Task<ResponseEntity> Snapshot(SQSEvent sqsEvent)
{
...

var record = sqsEvent.Records.First();
var parameter = JsonConvert.DeserializeObject<SqsEventBodyEntity>(record.Body);

//// 拍照
snapshotService.Do(parameter.TSCode, parameter.SalePageId);

...
}

要接收 SQS 來的命令, AWS SDK 有提供 SQSEvent 類別,傳入自訂的參數會放在 Records 中,我們是將要拍照的參數用 Json 組起來後透過 SQS 拋進來,所以讀出 Records 後返解 Json String 為 Object ,然後拋給實作的SnapshotServiece。

Snapshot 這個方法回傳值為自定義的,可以依照自己需求調整,避免誤會所以特別說明一下

1
2
3
4
5
6
7
8
/// <summary>
/// 執行快照
/// </summary>
/// <param name="sqsEvent">The SQS event.</param>
/// <returns>執行結果</returns>
public bool Snapshot(SQSEvent sqsEvent)
{
}

Chrome Headless

什麼是 Chrome Headless ,簡單說就是透過無介面的方式要求Chrome執行一些快照、讀取頁面的服務。

為了能夠執行驅動 Chrome,所以這邊安裝 Selenium 來輔助

/images/20181011/3.png

Selenium 還需要搭配 Chorme Driver 來操作 Chrome,所以需要先去http://chromedriver.chromium.org/下載,但需要特別注意的是要下載 Linux64 的版本,因為 AWS Lambda 是 Linux 環境

/images/20181011/4.png

實作

這邊將下載回來的 Chrome Driver 放在 Root 的資料夾,所以在建立 ChromeDriver Instance 需要告訴它 Driver的位置

/images/20181011/5.png

1
2
3
4
5
6
7
8
9
//chrome headless的參數
ChromeOptions options = new ChromeOptions();
options.AddArgument("no-sandbox");
options.AddArgument("headless");

//取得Driver的位置
var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

var driver = new ChromeDriver(root, options);

拍照

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...

//等待Timeout的設定
WebDriverWait _wait = new WebDriverWait(driver, new TimeSpan(0, 0, 10));

//要連的網址
driver.Navigate().GoToUrl(targetUrl);

//尋找要拍的Element Class
var targetElement = _wait.Until<IWebElement>(d => driver.FindElement(By.ClassName(targetElementClass)));

//因為要拍的區塊可能超過預設螢幕大小,所以這邊抓到Element後將它的寬高設定給Chrome Windows Size
driver.Manage().Window.Size = new Size(targetElement.Size.Width, targetElement.Size.Height);

// 拍照
Screenshot screenShot = ((ITakesScreenshot)driver).GetScreenshot();

//處理圖片存到哪邊,我是存到S3,但因為不是本篇重點所以省略後面的程式碼
var imgStream = new MemoryStream(screenShot.AsByteArray, 0, screenShot.AsByteArray.Length);

...

Docker 測試

打開 Power Shell,先確定已正確安裝 Docker

1
$ docker --version

/images/20181011/6.png

將目錄移到 .csproj 那層,然後執行

1
$ dotnet publish -c release -r linux-x64

dotnet publish command : 文件

如果發行成功應該可以在 \bin\release\netcoreapp2.1\linux-x64\publish 找到發行好的檔案

透過 Docker Image 測試執行

1
$ cat test.json | docker run -v ${PWD}/bin/QA/netcoreapp2.1/publish:/var/task/ -i -e DOCKER_LAMBDA_USE_STDIN=1 lambci/lambda:dotnetcore2.1 xxx.Slsa.Snapshot.Console::xxx.Slsa.Snapshot.Console.SqsHandler::Snapshot

解釋一下 Command

cat test.json : 這是拿來模擬 SQS 的呼叫時傳遞的參數檔案,檔案內容如下,我會一直替換Body 的值來測試程式是否正確去拍我要求拍的頁面,所以可以理解成是把檔案讀取後丟到後面的指令執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"Records":[
{
"messageId":"xxxxxxxx",
"receiptHandle":"xxxxx",
"body":"{\"SalePageId\": 12345,\"TSCode\": \"abcde\"}",
"attributes":{
"ApproximateReceiveCount":"x",
"SentTimestamp":"xxxx",
"SenderId":"xxxx",
"ApproximateFirstReceiveTimestamp":"xxxxxx"
},
"messageAttributes":{
},
"md5OfBody":"xxxxx",
"eventSource":"aws:sqs",
"eventSourceARN":"xxxxxx",
"awsRegion":"xxxxxx"
}
]
}

docker run -v ${PWD}/bin/QA/netcoreapp2.1/publish:/var/task/ : ${PWD} 為預設變數,表示目前的目路位置,加上 /bin/QA/netcoreapp2.1/publish 後就是剛剛發行的檔案位置,mount 到 Docker container 裡面的 /var/task 位置,而這也是 lambda 預設執行的位置

lambci/lambda:dotnetcore2.1 : Docker Image 的名稱,這是別人已經做好跟 AWS lambda 一模一樣環境的 Image ,透過它來建置我們要的環境做測試

xxx.Slsa.Snapshot.Console::xxx.Slsa.Snapshot.Console.SqsHandler::Snapshot : 格式為 Assembly Name :: Class Name :: FunctionName,表示請它執行剛剛我們開發的 SQSHandler 的 Snapshot 方法

找不到 Chrome binary 錯誤

這時候應該會發生找不到 Chrome binary 的錯誤,原因是在 Lambda 的環境並不能安裝 Chrome,所以 Chrome Driver 想去預設環境找 Chrome 核心來執行時會找不到。

/images/20181011/7.png

整個驅動 Chrome 的流程

Selenum → Chrome Driver → Chrome binary

還好已經有網路上的大神解決了這問題,serverless-chrome這個 Project 就是將 Chrome 封裝成 Binary 檔後可以打包進專案中,在 Power Shell 執行以下指令來取得 Chrome Binary

1
2
3
$ docker run -dt --rm --name headless-chromium adieuadieu/headless-chromium-for-aws-lambda:stable
$ docker cp headless-chromium:/bin/headless-chromium ./
$ docker stop headless-chromium

在目前 Power Shell 指的資料底下可以找到 headless-chromium 檔案,將這個檔案加到專案中

/images/20181011/8.png

1
2
3
4
5
6
7
8
9
10
11
12
//chrome headless的參數
ChromeOptions options = new ChromeOptions();
options.AddArgument("no-sandbox");
options.AddArgument("headless");

//取得Driver的位置
var root = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

//指定Chrome Binary位置
options.BinaryLocation = Path.Combine(root, "headless-chromium");

var driver = new ChromeDriver(root, options);

做到這邊再執行剛剛的指令應該可以正常拍照了。

掃雷

亂碼問題

因為 Chrome Binary 輕量化,所以作者並沒有把中文/日文/韓文包進來,所以拍一下遇到上述文字都會出現亂碼

依據該討論串下方的回應,已經有對應的解決方案

/images/20181011/9.png

1.將需要的字典檔下載回來放在 Root 的 .fonts 資料夾中

/images/20181011/10.png

2.將 $HOME 環境變數指到 /var/task

建立一個檔案 env.variable 裡面寫

1
HOME=/var/task

接著透過 Docker Container 執行

1
$ cat test.json | docker run --env-file ./env.variable -v ${PWD}/bin/QA/netcoreapp2.1/publish:/var/task/ -i -e DOCKER_LAMBDA_USE_STDIN=1 lambci/lambda:dotnetcore2.1 xxx.Slsa.Snapshot.Console::xxx.Slsa.Snapshot.Console.SqsHandler::Snapshot

這樣拍出來的中文、韓文、日文的畫面就不會亂碼了

權限

將程式打包好放到 AWS Lambda 上面跑,隨即碰到以下錯誤

1
2
3
4
5
6
7
8
9
10
11
12
Permission denied: Win32Exception
at Interop.Sys.ForkAndExecProcess(String filename, String[] argv, String[] envp, String cwd, Boolean redirectStdin, Boolean redirectStdout, Boolean redirectStderr, Boolean setUser, UInt32 userId, UInt32 groupId, Int32& lpChildPid, Int32& stdinFd, Int32& stdoutFd, Int32& stderrFd, Boolean shouldThrow)
at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start()
at OpenQA.Selenium.DriverService.Start()
at OpenQA.Selenium.Remote.DriverServiceCommandExecutor.Execute(Command commandToExecute)
at OpenQA.Selenium.Remote.RemoteWebDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Remote.RemoteWebDriver.StartSession(ICapabilities desiredCapabilities)
at OpenQA.Selenium.Remote.RemoteWebDriver..ctor(ICommandExecutor commandExecutor, ICapabilities desiredCapabilities)
at OpenQA.Selenium.Chrome.ChromeDriver..ctor(ChromeDriverService service, ChromeOptions options, TimeSpan commandTimeout)
at .........

原因是 Lambda 的 /var/task 是 read-only,所以要執行 Chrome Driver 與 Chrome Binary 時會沒有權限,只好用很 tricky 的方式,先把 Chrome Driver 與 Chrome Binary 搬到 /tmp 底下再授予權限

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
36
37
38
39
40
/// <summary>
/// Isinitialeds this instance.
/// </summary>
private void Initial()
{
if(!File.Exists("/tmp/chromedriver"))
{
File.Copy("/var/task/chromedriver", "/tmp/chromedriver", true);
Exec("chmod +x /tmp/chromedriver");
}

if(!File.Exists("/tmp/headless-chromium"))
{
File.Copy("/var/task/headless-chromium", "/tmp/headless-chromium", true);
Exec("chmod +x /tmp/headless-chromium");
}
}

/// <summary>
/// Executes the specified command.
/// </summary>
/// <param name="cmd">The command.</param>
private void Exec(string cmd)
{
var escapedArgs = cmd.Replace("\"", "\\\"");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\""
}
};
process.Start();
process.WaitForExit();
}
1
2
3
4
5
6
7
8
9
//chrome headless的參數
ChromeOptions options = new ChromeOptions();
options.AddArgument("no-sandbox");
options.AddArgument("headless");

//指定Chrome Binary位置
options.BinaryLocation = "/tmp/headless-chromium";

var driver = new ChromeDriver("/tmp", options);

CreatePlatformSocket() Operation not permitted (1)

接著遇到下個問題是, ChromeDriver Init 起來時會去呼叫 http://localhost:xxxx/Session ,而這時後就會因為沒有權限而失敗,但這個在 NodeJs 的版本不會發生,查了 Selenium 的 Source Code 也看不出原因,找了相當多的討論串,目前似乎依然無解,也是在這一題後不得不先放棄,改採用相容較高的 NodeJs 版本

/images/20181011/11.png

2018-10-25 更新

在產生 ChromeDriver 的時候加上 disable-dev-shm-usagesingle-process 兩個參數可以解決這個問題,

1
2
3
4
5
options.AddArguments("no-sandbox", "headless", "disable-dev-shm-usage", "disable-gpu", "single-process", "no-zygote", "hide-scrollbars", "lang=zh-TW,zh");

options.BinaryLocation = "/tmp/headless-chromium";

var driver = new ChromeDriver("/tmp", options);

其中 single-process 為 Chromium 的模式之一,詳細可以參考文件

disable-dev-shm-usage

1
To fix, run the container with docker run --shm-size=1gb to increase the size of /dev/shm . Since Chrome 65, this is no longer necessary. Instead, launch the browser with the --disable-dev-shm-usage flag

會找到這個解法是因為去看了 serverless-chrome 的 source code 發現他是這樣寫的,但這樣搭配為什麼會正常,說實在的目前我還看不出為什麼 , 只能等之後讀更多文件再看看有沒有辦法解答。

加上這兩個參數後 Lambda 上面雖然還是會報 CreatePlatformSocket() Operation not permitted (1) 錯誤,但減少到只剩下兩次,就正常執行了

/images/20181011/12.png

補充 : 環境問題

最後雖然我們採用了 NodeJs 的版本,但還是踩了一個小小的雷,那就是要把檔案壓縮成 zip 檔放到 AWS Lambda 時,相同的程式用我的電腦壓出來是不能執行,但同事壓出來的 zip 檔案卻可以,經過測試後發現,因為我的 Mac 語系環境是中文,壓縮出來時預設會是封裝.zip,不管是中文的的放上去,或是改了檔名後放上去都會錯誤。

只能下 command 指定壓縮出來的檔名才可解決這個問題

1
$ zip -r -j ./destination/pacakge ./source

相同的,在 Windows 壓出來放上去一樣會壞掉,推測是相同原因….真的是雷到你不要不要的

2018-10-25 更新

網路上找到有人討論 Windows 壓縮後放上去會壞掉的問題,解法是請安裝 7zip,並且直接到 root 層執行壓縮,壓完後解壓就要是Root,不要再包一層資料夾,另外…只能用 GUI 執行壓縮,我用 PowerShell 壓縮一樣不行,不知道為什麼(暈),難怪論壇開宗明義第一句話就是,不要用 Windows ,Linux base 的東西跟 windows 真的是很不合…

/images/20181011/13.png

常常會有需要查詢英文、日文單字的情形,以前都會先打開Yahoo或是Google的辭典然後查詢單字,直到今天學到了自訂Chrome Search Engine實在太好用了,所以決定寫下來記錄一下

#設定

首先先打開Chrome瀏覽器,在網址列輸入

1
chrome://settings/searchEngines

接著點選**[新增]**

/images/20180914/1.png

/images/20180914/1.png

  • 搜尋引擎

    ​ 讓自己識別的名稱,可以隨便寫

  • 關鍵字

    ​ 這個是你啟動自訂Google Search Engine的關鍵字,請寫自己好記的

  • 網址

    告訴Chrome如何用你下的關鍵字去搜尋,以Google翻譯為例,當你輸入apple時網址會變成如圖,而紅框處就是我們要替換的地方,請以%s替代

    /images/20180914/1.png

#使用

之後只要我在Chrome瀏覽器輸入gdic+空格,就會變成這樣

/images/20180914/1.png

再次輸入Apple後Enter就會跑到Google翻譯頁並查好了

/images/20180914/1.png

最近開發了一支收集資訊然後將資料轉成Json檔案給其它單位讀取,但收到對口單位回報Json格式不正確。但我將檔案內容貼到線上的Json解析網站,或是自己肉眼判斷都覺得格式沒問題,所以執行了一次對方的解析程式,錯誤訊息如下。

在字串0的位置有錯誤

/images/20180823/1.png

打開我提供的檔案,字串0也就是最前面感覺很正常…

/images/20180823/2.png

接著我將這段錯誤訊息貼Notepad++,轉成Hex碼來看看究竟是藏了什麼東西

/images/20180823/3.png

#什麼是BOM

ef bb bf拿去Google最後查到了Wiki-位元組順序記號

節錄Wiki

位元組順序記號(英語:byte-order mark,BOM)是位於碼點U+FEFF統一碼字元的名稱。當以UTF-16UTF-32來將UCS/統一碼字元所組成的字串編碼時,這個字元被用來標示其位元組序。它常被用來當做標示檔案是以UTF-8UTF-16UTF-32編碼的記號。

#解法

而會塞入位元組序是因為在寫檔案時這樣指定編碼

1
2
3
4
5
6
using (StreamWriter file = new StreamWriter(
new FileStream(path,FileMode.OpenOrCreate,FileAccess.Write),
Encoding.UTF8))
{
.....
}

只要將寫改改成 new UTF8Encoding(false) 即可

1
2
3
4
5
6
using (StreamWriter file = new StreamWriter(
new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write),
new UTF8Encoding(false)))
{
.....
}