Quantcast
Channel: 黑暗執行緒
Viewing all 2311 articles
Browse latest View live

Dapper出現sql_variant is incompatible with ntext

$
0
0

有個古老資料庫,裡面還有NTEXT型別欄位(SQL 2005加入NVARCHAR(MAX)後,應該沒人想用TEXT/NTEXT了),用Dapper執行一段SQL更新NTEXT欄位,發生古怪錯誤。

指令如下:

cn.Execute("UPDATE SomeTable SET NTextField = @data WHERE Id = 1", new { data = "…" });

錯誤訊息為:sql_variant is incompatible with ntext

System.Data.SqlClient.SqlException (0x80131904): Operand type clash: sql_variant is incompatible with ntext
   於 System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   於 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   於 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   於 System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   於 System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   於 System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
   於 System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
   於 System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
   於 System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   於 Dapper.SqlMapper.ExecuteCommand(IDbConnection cnn, CommandDefinition& command, Action`2 paramReader) 於 D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs: 行 2802
   於 Dapper.SqlMapper.ExecuteImpl(IDbConnection cnn, CommandDefinition& command) 於 D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs: 行 1060
   於 Dapper.SqlMapper.Execute(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Nullable`1 commandTimeout, Nullable`1 commandType) 於 D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs: 行 995
   於 Managers.ApiService.UpdateBlah(Guid id, String userId, String reason) 於 X:\TFS\Boo\MAIN\src\WebApi\ApiService.cs: 行 847

第一次看到sql_variant型別,依文件所說,sql_variant常用於資料行、參數、變數及使用者定義函數的傳回值中,不能用來儲存varchar(max)、nvarchar(max)、text、ntext、xml… 等,最大長度為8016 Bytes。

推測Dapper在底層用sql_variant來存放@data參數字串,在指定給NTextField欄位時產生錯誤。爬文找不到有人反應類似問題,猜想原因是會Dapper的新世代不會摸到NTEXT,而存取NTEXT欄位的程式還停在ASP、VB6、ADO.NET世界打滾,像我這樣用3D印表機印尪仔標,算是少數中的少數。 XD

福至心靈,既屬Dapper內部行為,這様算Bug嗎?如果是Bug,後來有修正嗎?用NuGet Manager查Dapper版本還停在1.26.0,而最新版已到1.42.0。

處理軟體茶包的SOP,先更新到最新版再說。更新至1.42.0,不藥而瘉,結案~


小密技-在IIS主機現場撰寫測試ASPX偵錯

$
0
0

ASP.NET Web Application Project(WAP)與 Web Site Project(WSP)之間有一段有趣的消長演進:ASP.NET 1.0/1.1時代的ASP.NET網站要先編譯成DLL才能執行,稱之為Web Application Project;ASP.NET 2.0起推出Web Site Project架構,採用Code-Beside,不需事先編譯,Blah.aspx與Blah.aspx.cs一起放上IIS網站就能運行。雖然開發者還是可以選擇用WAP寫網站,但WSP改完存檔就能立刻看結果顯然比較迷人,於是WSP成為較多人的選擇。而到了ASP.NET MVC時代,Controller架構依賴事先編譯,加上WAP同時支援WebForm、MVC、WebAPI,於是WAP再次取代WSP回歸主流。延伸閱讀:Web Site Project vs Web Application ProjectWeb Site Project為何沒落?

近幾年來,新專案都已改用WAP,WebForm、MVC進可攻退可守,網頁執行前都需要編譯,WSP那個「寫兩行程式存檔就能看測試結果」的時代似乎已一去不返… 其實沒有!ASPX即時編譯的功能一直都在,也是本篇要分享的IIS網站除錯小密技。

實務上,不少場合我們還是非常需要ASPX的「隨寫隨測」功能,像是射IIS茶包,開Notepad寫幾行測完,絕對完勝開Visual Studio改程式編成DLL再丟到IIS,快了豈止十倍。舉幾個我自己遇過的例子:

  • 懷疑正式台因appSetting設定值不對出錯,想檢查設定值是否為預期內容
  • 想做個TransactionScope包DbConnetion測試確認分散式交易環境正常
  • 在正式台想知道實際使用的ODP.NET元件版本、DLL路徑
  • 列舉檢查正式主機是否欠缺必要的DbProvider

以上狀況,都要靠寫幾行程式丟上主機執行獲得解答。在測試開發環境還簡單,用Visual Studio寫幾行程式部署到IIS執行便知分曉。若問題發生在正式環境,部署過程會複雜一些,例如:若上版流程已進化到只能透過自動編譯部署,為了查問題測個小東西要簽入版控重上程式,等測完再還原未免荒謬。而從本機另編一顆DLL送上正式機,測完再換回來也沒簡單到哪裡去。

面對類似情境,我會在IIS Web的目錄新増一個test.aspx,用Notepad記事本輸入以下內容:(以檢測DbProvider為例)

<%@ Page Language="C#" %>
<ol>
<%
  System.Data.DataTable dt =
  System.Data.Common.DbProviderFactories.GetFactoryClasses();
for (int i = 0; i < dt.Rows.Count; i++)
    Response.Write("<li>" + dt.Rows[i][2] + "</li>");
%>
</ol>

薑!薑!薑!薑~

是不是重現了WSP時代那種「隨寫隨測」的敏捷性?而且測完刪除test.aspx就恢復原樣(提醒:記得要將測試程式刪除,以免被誤用產生風險),省去置換DLL還要還原的麻煩,又可以現改現看,輕巧方便許多。

最後再補充兩則小技巧,應該很多人像我一樣,用Notepad少了Intellisense就不會寫程式,所以我會在本機開Visual Studio把程式寫好,再複製貼到遠端桌面環境。另外,在Code-Behind中我們常用using省去打一堆Namespace,在Inline ASPX裡可用<%@ Import Namespace="…" %>宣告產生相同效果,例如:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.Common" %>
<ol>
<%
  DataTable dt = DbProviderFactories.GetFactoryClasses();
for (int i = 0; i < dt.Rows.Count; i++)
    Response.Write("<li>" + dt.Rows[i][2] + "</li>");
%>
</ol>

雖然沒什麼學問,卻是十分簡單好用的小技巧,下回再遇到正式環境IIS茶包,大家該知道如何在ASP.NET網站現場撰寫測試程式偵錯了吧?

NLog問題偵錯技巧

$
0
0

NLog是我們開發團隊的奧林匹克指定Log元件,但經驗裡遇過不少次沒有寫Log檔的狀況,而NLog為了避免寫Log過程出錯導致主程序中斷,預設不會拋出錯誤訊息,這讓NLog茶包特別難找。過去較常見問題是對Log資料夾缺少寫入權限(尤其是IIS 7.5+會用IIS APPPOOL\XXXX虛擬帳號,需要額外開權限),補設權限後就OK,對NLog問題如何除錯也未多深究。不料前幾天踢到鐵板,足足卡了一小時找出原因(代表以前是假會,只要不是權限問題就卡關,嗚~),不過因此學會NLog異常排除技巧是意外收獲。

NLog預設會忽略所有發生的錯誤(包含NLog.config壞掉都假裝沒事)以免干擾主程式執行,當需要偵錯時,NLog.config有個開關:<nlog throwExceptions="true" />,開啟後便知NLog為什麼沒成功寫入Log。

來個測試,故意移除Log資料夾寫入權限,並加上throwExceptions="true",錯誤訊息與爆炸程式行數一目瞭然:

除了設定NLog在出錯時直接拋出錯誤,還有另一種有效蒐集錯誤訊息的做法:<nlog internalLogLevel="Debug" internalLogFile="c:\temp\nlog-internal.log">,透過internalLogLevel及internalLogFile開啟NLog的內部Log,從中也可找出NLog無法寫入Log的原因(如下圖)。與throwExceptions="true"有以下不同:

  1. internalLogFile錯誤訊息的StackTracce資訊從NLog元件內部開始(在下例為NLog.Tragets.Target.Write),追不到logger.Debug()等寫入Log的程式來源。
  2. 若internalLogFile路徑遇上無權寫入,一樣暴斃驗無傷。
  3. internalLogFile不只可以記錯誤訊息,當internalLogLevel設成Debug、Trace等模式,還能看到更多NLog運作資訊。

同場加映一則小技巧-如何快速找出Target f的檔案路徑?結合先前介紹過的取得NLog檔案路徑以及在IIS主機現場撰寫測試ASPX偵錯,以下程式顯示LoggerName為"AAA"時的Log路徑,並試寫一個Debug Log,第一行NLog.LogManager.ThrowExceptions = true 可取代 <nlog throwExceptions="true">設定,在NLog出錯時顯示錯誤訊息。透過此一測試可以確認Log檔案位置以及寫入權限。

<%@ Page Language="C#" %>
<%
NLog.LogManager.ThrowExceptions = true; 
Response.Write( 
(NLog.LogManager.Configuration.FindTargetByName("f") as NLog.Targets.FileTarget).FileName 
.Render(new NLog.LogEventInfo() { TimeStamp = DateTime.Now, LoggerName = "AAA" }) 
); 
NLog.LogManager.GetLogger("AAA").Debug("TEST"); 
NLog.LogManager.Flush(); 
%>

還有一點,NLog.config在修改後,需要重啟Web Application才會生效,有個<nlog autoReload="true">可在NLog.config修改時自動載入,可在不中斷ASP.NET或Windows程序調整NLog設定。

【結論】

  1. NLog預設會壓抑所有NLog錯誤以免中斷主要程式邏輯,設定<nlog throwExceptions="true">可顯示NLog錯誤。
    提醒:問題排除後建議改回false,或用在測試程式中以NLog.LogManager.ThrowExceptions=true替代。
  2. 遇到NLog問題時,可寫一個迷你NLogTester.aspx檢查路徑及寫入權限。(範例請見文章)
  3. <nlog autoReload="true">可設定NLog.config修改後自動更新設定,省去重啟程序的困擾。

【延伸閱讀】

  1. Configuration file · NLog-NLog Wiki · GitHub
  2. Logging Troubleshooting · NLog-NLog Wiki · GitHub

【後記】

我這次遇到的狀況是程式換版,將已平測一段時間的新版資料夾取名取代舊版(包含NLog.config),後來想到要沿用原本NLog.config指定的Log路徑,再把舊版的NLog.config覆寫回去,從此再也沒看到有Log寫入。該Log路徑在換版前原本就運作良好,不該是權限問題,讓過去只會處理NLog DLL版本衝突跟Log目錄權限茶包的我黔驢技窮,瞎試半天無解,爬文學會開啟throwExceptions才水落石出:

Exception Details: System.IO.FileNotFoundException: Could not load file or assembly 'NLog.Targets.Syslog' or one of its dependencies. The system cannot find the file specified.

舊版的NLog.config設定了Syslog Target,如下例:

<?xmlversion="1.0"encoding="utf-8" ?>
<nlogxmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<extensions>
<addassembly="NLog.Targets.Syslog"/>
</extensions>
<targets>
<targetxsi:type="File"name="f"fileName="D:/Log/${logger}/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}"encoding="utf-8"/>
<targetname="syslog"type="Syslog"syslogserver="syslog-server-ip"
port="514"facility="Local7"
sender="BLAH.UAT"layout="${longdate} ${uppercase:${level}} ${message}"/>
</targets>
<rules>
<loggername="*"minlevel="Trace"writeTo="f,syslog"/>
</rules>
</nlog>

extensions引用了NLog.Targets.Syslog,舊版bin下有NLog.Targets.Syslog.dll,忘了複製到新版bin目錄導致錯誤,補上後問題排除。

首遇TFS自動合併出錯案例

$
0
0

換用TFS版控時我們開始採用「多重簽出」原則,大幅改善VSS時代「專案一被人簽出其他人就動不了」的困擾。但隨之而來的副作用是:多人同時修改,若簽入時別人已先簽入更新的版本,就需要執行程式碼合併。

在我們的經驗裡,TFS有個神奇又方便的「自動合併」功能,只要程式修改幅度不大,沒有改到同一段程式,TFS幾乎都能正確自動合併,不需人為介入,少數難以判別的情況才會跳出提示要求人工處理。

但時間久了,我不免懷疑,程式碼合併的情境百百種,肉眼合併都難保沒有疏漏,演算法要怎麼寫才能不出錯?但這一兩年下來,記憶中都還真沒出過亂子,TFS自動合併演算法直逼AlphaGo呀,令人讚嘆不已… 直到我膝蓋中了一箭!

前幾天在合併分支時出現一起TypeScript屬性重複案例,追查發現是自動合併造成的。有一段CodeGen產生的TypeScript Model定義,因故調換屬性順序,自動合併時調換位置的屬性出現兩次釀禍。

用實例說明比較好理解,注意下例TypeScript型別的SomePrz及SomeQty屬性,原本排在ColB之後,ColC之前:

export class Blah extends ViewModelBase {
/** 欄位A */
    ColA: KnockoutObservable<any> = ko.observable();
/** 欄位B */
    ColB: KnockoutObservable<any> = ko.observable();
//...省略...
/** 價格 */
    SomePrz: KnockoutObservable<any> = ko.observable();
/** 數量 */
    SomeQty: KnockoutObservable<any> = ko.observable();
/** 欄位C */
    ColC: KnockoutObservable<any> = ko.observable();
/** 欄位D */
    ColD: KnockoutObservable<any> = ko.observable();
/** 欄位E */
    ColE: KnockoutObservable<any> = ko.observable();
/** 欄位F */
    ColF: KnockoutObservable<any> = ko.observable();
/** 欄位G */
    ColG: KnockoutObservable<any> = ko.observable();
/** 欄位H */
    ColH: KnockoutObservable<any> = ko.observable();
//...省略...
}

Branch版本裡類別增加了幾個額外屬性,並調動了SomePrz及SomeQty順序,由ColC前方移至ColE後方:

export class Blah extends ViewModelBase {
/** 欄位A */
    ColA: KnockoutObservable<any> = ko.observable();
/** 欄位B */
    ColB: KnockoutObservable<any> = ko.observable();
//...省略...
/** 欄位C */
    ColC: KnockoutObservable<any> = ko.observable();
/** 欄位D */
    ColD: KnockoutObservable<any> = ko.observable();
/** 欄位E */
    ColE: KnockoutObservable<any> = ko.observable();
/** 價格 */
    SomePrz: KnockoutObservable<any> = ko.observable();
/** 數量 */
    SomeQty: KnockoutObservable<any> = ko.observable();
/** 欄位F */
    ColF: KnockoutObservable<any> = ko.observable();
/** 欄位G */
    ColG: KnockoutObservable<any> = ko.observable();
/** 欄位H */
    ColH: KnockoutObservable<any> = ko.observable();
//...省略...
}

Merge回主線時,由TFS自動合併解決衝突,變成以下的樣子,SomePrz/SomeQty在ColC欄位前方及ColE後方各出現一次。

export class Blah extends ViewModelBase {
/** 欄位A */
    ColA: KnockoutObservable<any> = ko.observable();
/** 欄位B */
    ColB: KnockoutObservable<any> = ko.observable();
//...省略...
/** 價格 */
    SomePrz: KnockoutObservable<any> = ko.observable();
/** 數量 */
    SomeQty: KnockoutObservable<any> = ko.observable();
/** 欄位C */
    ColC: KnockoutObservable<any> = ko.observable();
/** 欄位D */
    ColD: KnockoutObservable<any> = ko.observable();
/** 欄位E */
    ColE: KnockoutObservable<any> = ko.observable();
/** 價格 */
    SomePrz: KnockoutObservable<any> = ko.observable();
/** 數量 */
    SomeQty: KnockoutObservable<any> = ko.observable();
/** 欄位F */
    ColF: KnockoutObservable<any> = ko.observable();
/** 欄位G */
    ColG: KnockoutObservable<any> = ko.observable();
/** 欄位H */
    ColH: KnockoutObservable<any> = ko.observable();
//...省略...
}

所幸,TypeScript為強型別,不允許屬性重複宣告,我們很快查出錯誤,合併Branch時暫時停用自動合併功能即可避開問題。

出問題的TypeScript檔案不小,有近2500行,而重複屬性的位置在1668行左右。事後我試著模擬屬性順序調動的情境,卻怎麼都無法重現自動合併錯誤,猜想需要夠大的檔案或某些特殊條件下才會導致自動合併誤判。

爬文沒有找到太多TFS自動合併錯誤的個案,有一個接近的案例是自動合併.csproj發生項目重複,原因也跟項目順序重排有關,證明調換順序是差異比對演算法的大魔王無誤!

這是我第一次遇到TFS自動合併失誤,說不上震憾,充其量只是證實「不存在完美的差異比對演算法」的假設,不致影響我對它的信心。就當成一記失投,它依然是我心目中的賽揚獎投手~ 經此經驗,未來使用自動合併時會提高警覺,預做它可能出錯的心理準備。

當然,如果你寧可辛苦一點也不想承擔任何出錯風險,可考慮將它關閉(如下圖),大家請自行拿捏。

【茶包射手日記】MSBuild.ILMerge.Task發生型別重複錯誤

$
0
0

讀者Peter回饋一起MSBuild.ILMerge.Task合併錯誤案例:專案引用Manatee.Trello.WebApi套件,其依賴Microsoft.AspNet.WebApi.Client.5.2.3(System.Net.Http.Formatting.dll)及Microsoft.AspNet.WebApi.Core.5.2.3(System.Web.Http.dll),合併時出現錯誤:ILMerge.Merge: ERROR!!: Duplicate type 'System.Net.Http.HttpRequestMessageExtensions' found in assembly 'System.Web.Http'. Do you want to use the /alllowDup option?

一般來說,Namespace加上型別名稱應該是唯一的,撞名實屬罕見,引發我的好奇想一探究竟。試開一個新專案引用Manatee.Trello.WebApi與MSBuild.ILMerge.Task,合併組件時也拋出相同錯誤訊息。能重現錯誤一切好辦,著手展開調查。

手動以ILMerge.exe合併組件也出現相同錯誤,依訊息補上/allowDup參數便可避免錯誤。

依此研判,問題出在要合併的組件內含完全相同的類別名稱!使用Telerik JustDecompile分析,果真在System.Net.Http.Formatting.dll與System.Web.Http.dll發現兩個同名型別-HttpRequestMessageExtensions與MediaTypeFormatterExtensions:

 

原來這兩個型別是宣告擴充方法(Extension Method)的靜態型別,應用時完全不會用到型別名稱,故在不同DLL重複出現也無妨;一旦具有同名型別的組件要合併,名稱重複問題才會浮上檯面。

由此可知,執行ILMerge.exe時加上allowDup參數即可解決問題,但一直沒找到文件示範如何為MSBuild.ILMerge.Task加上allowDup參數。歷經一番研究,我發現合併作業的核心邏輯寫在MSBuild.ILMerge.Task.dll,反組譯發現其中有實作AllowDuplicateType參數,但似乎只能透過targets設定檔指定。

在packages\MSBuild.ILMerge.Task.1.0.2\build\MSBuild.ILMerge.Task.targets找到<MSBuild.ILMerge.Task>,加上AllowDuplicateType = "*"(或是明確列出已知的重複名稱型別,如AllowDuplicateType = "HttpRequestMessageExtensions,MediaTypeFormatterExtensions"),等同為ILMerge.exe加上/allowDup參數。(註:此種參數修改做法影響範圍將涵蓋整個解決方案)

修改後重新編譯,組件成功合併,問題排除,收工~

【茶包射手日記】無法使用別名登入本機IIS

$
0
0

前陣子研究出克服入口網站內嵌其他網站跨網站存取限制的方法,實際會用於整合兩台以上網站,但在開發測試期間也要搞兩台機器太麻煩,於是我用了點技巧,在windows/system32/drivers/etc/hosts加入額外設定:

127.0.0.1    portal.dev.net
127.0.0.1    webap.dev.net
127.0.0.1    mybox

如此開發機的IIS就可一人分飾多角,用 httq://portal.dev.net/ 及 httq://webap.dev.net/ 都能連上,還會被瀏覽器視為不同網域機器,模擬正式環境的情境。經初步測試成功就以為天衣無縫,沒想到今天踢到鐵板。

初期開啟匿名存取,測試一切順利,到第二階段改用Windows驗證,卻發現使用localhost、127.0.0.1、機器名稱登入正常,一旦改用 portal.dev.net 或 webap.dev.net,甚至 mybox,網頁會不斷彈出帳號密碼視窗,帳號密碼正確也無法登入。進行對照實驗,若改從另一台機器測試(在hosts加入同樣項目指向我的LAN IP)可正常登入,歸納出「使用localhost、127.0.0.1、機器名稱或FQDN以外的URL登入本機IIS,將無法通過Windows驗證」的結論。

頓時心頭涼了半截,難道為此真得搞另一台機器進行測試?

爬文找到線索:MS KB有篇討論用FQDN或自訂Web名稱無法登入IIS的文章提及類似場景。文中提到Windows 2003 SP1起啟用一種名為Loopback Check的安全機制,藉以防止本機惡意程式模擬外來連線進行攻擊。KB提到兩種解決方法:修改Registry列舉會使用的別名或停用LoopBackCheck。前者採白名單開放較嚴謹,後者一次搞定較乾脆,衡量停用檢查增加風險有限,決定使用後者:

加入Registry後IISREST,終於能用portal.dev.net登入本機,再次驚險過關。

【茶包射手日記】詭異的Oracle Client 32/64版本錯誤

$
0
0

同事報案,某台測試機器原本只裝Oracle Client 32位元版本,因該主機上的SQL Server x64需建Linked Server連Oracle,故加裝了Oracle Client 64位元版本[參考]。不料,同主機用System.Data.OracleClient讀資料的網頁,在安裝Oracle Client 64位元版後,忽然冒出Oracle 32/64版本不符的經典錯誤:An attempt was made to load a program with an incorrect format。

依先前研究,.NET能依PATH尋找正確版本,確認32及64位元的Oracle Client路徑都有設定,檢查IIS AppPool確定已開啟32位元模式,不應出錯。而且原本網站正常,加裝64位元Oracle Client後出錯尤其難以解釋,案情陷入膠著…

招喚茶包一哥-Process Monitor登場,很快找出問題:IIS AppPool虛擬帳號對Oracle Client目錄下的oci.dll沒有讀取權限,補開權限後,問題終於排除。由於網站設定並未變動,先前可以執行代表原本有開權限,為什麼安裝64位元Oracle Client會變更32位元Oracle Client目錄的權限,則是個謎。但在經驗中,因為Oracle Client目錄權限出錯已不是第一次,相信也不是最後一次。  而從本例可多學到一條「依PATH路徑尋找Oracle Client適合版本」運作邏輯,在遇到存取權限問題時,不會直接爆出存取被拒,而是略過錯誤導致錯選版本。

想想,這次根本是總複習來著,看看我動用多少知識與經驗:

有種動用畢生所學跟茶包對決的感覺 XD 而面對Oracle Client版本罄竹難書的肇事記錄,我暗暗握拳立誓:

拎杯以後都要改用Managed ODP.NET

拎杯以後都要改用Managed ODP.NET

拎杯以後都要改用Managed ODP.NET

使用Fiddler竄改網頁內容進行測試

$
0
0

瀏覽器F12開發者工具內建的網頁封包分析功能日益強大,但老牌HTTP封包傳輸偵錯神器-Fiddler,還是有不少獨門絕活為F12工具遠遠不及,例如先前研究NTLM與Kerberos驗證時用到的Inspector/Auth資訊分析。今天則介紹Fiddler另一項少為人知但強大的功能-AutoResponder。

用F12工具修改網頁DOM、CSS,甚至JavaScript都不是問題,但只能在網頁載入後事後修改,不符合某些情境要求。例如,瀏覽別人寫的網頁發現問題,我想調動兩個<script src="…">的先後順序診斷時間差問題,或是想禁止載入或換掉某個css檔觀察變化,這些就無法靠F12工具實現。Fiddler的AutoResponder允許你制定規則,在瀏覽器發出特定Request時不由Web Server取內容,改傳回指定內容。

用個實例演練,前幾天剛好有網友問到jQuery自動完成懶人包的問題,就拿它的Live Demo當範例,展示如何在網頁中加料。首先開啟Fiddler擷取內容,共有default.htm、jquery-1.3.2.js、jquery.autocomplete.js跟jquery.autocomplete.css四個檔案,我們想修改的對象是default.html。

第一步是先取得Response的完整內容,最簡便的做法是在封包按右鍵選單,選擇Save/Response/Entire Response另存成文字檔:

接著我們在AutoResponder建立一條規則,先選取右方清單中的default.htm項目,按下Add Rule,Fiddler會自動產生EXACT:httq://…比對條件(EXACT代表URL要完全相同,比對時可使用萬用字元甚至Regular Expression,詳見官方文件),接著指定回應內容,選擇"Find a file"找到前一步驟儲存的文字檔。接著勾選「Enable automatic responses」及「Unmatched requests passthrough」,前者啟用自動回應功能,後者則是因為我們只針對default.htm訂了規則,要略過其他不符規則的Request,繼續交由Web Server處理。

設定好後,重新瀏覽,一切看似正常沒什麼不同,但背後default.htm的內容已不是來自Web Server,而是我們剛才存檔的IndexHtml_Response.txt。接著來動點手腳,用Notepad++修改內容,在<body>前方加入五個字「破解樂無窮」。

但有一點要注意,HTTP Response規格中,需透過Content-Length Header宣告資料長度,在修改內容後記得要調整Content-Length,瀏覽器才不會錯估長度導致部分內容被截斷。(記得UTF-8下的中文字元一個要算3個Bytes)

修改後,再重新整理瀏覽器,薑薑薑薑~

有沒有當駭客的fu,很有趣吧?

最後補充,除了換成自訂的Response內容,Fiddler也提供了HTTP 200、304、401、404… 等標準回應、導向其他URL功能、執行FiddlerScript,甚至有接到Request後中斷執行再動態挑選回應檔案的花式玩法,有興趣的朋友可以參閱官方文件


【笨問題】IIS HTML、圖檔傳回空白內容

$
0
0

家裡的Windows 10不常用來開發,前陣子寫MVC測試入口網站內嵌其他網站跨網站還正常,但在IIS放個test.html時,卻出現奇怪現象。

使用瀏覽器存取test.html,內容一片空白。怕是新增的網頁有問題,用IIS wwwroot原本就有的iisstart.png總該OK吧?登楞~一樣傳回空白。

開啟Fiddler偵錯,進行幾個對照測試,觀察到結果如下:

  • 存取png及html時傳回HTTP 200,但Content-Length為0,沒有內容
  • 故意打錯URL,會得到HTTP 404,但頁面依然空白
  • 若存取的是aspx,網頁可以顯示

摸索十分鐘後恍然想起,是這台IIS沒裝好!IIS 7預設不主動安裝「靜態內容」,需額外勾選:

笨點在於這問題之前就遇過(參考:不厭其詳的IIS7安裝項目),當時忘了用力強調Content-Length: 0的關鍵特徵,導致再次遇上沒能想起。特以此文補刀,以保此生不忘~

搞懂X-UA-Compatible IE相容設定

$
0
0

始終沒認真搞懂X-UA-Compatible IE=7跟IE=EmulateIE7有什麼不同,最近專案要步入「古今合壁」的偉大工程,把許多IE Only的活化石網頁跟HTML5網頁摻在一起做瀨尿牛丸,再不弄清楚恐會死得難看,於是查了文件做了實驗,筆記備忘。

MSDN文件 Defining document compatibility

所有IE包含三種文件模式:

  • Standard Mode(標準模式)
    努力支援最新HTLM5/CSS3/SVG等標準,但一如大家所知,不同版本IE支持程度不同
  • Quirks Mode(接縫模式)
    力求相容較早版本瀏覽器的行為 [補充資料]
    在IE9以前,Quirks Mode相容範圍的對象限於IE5.5,IE10起則擴大到HTML5規範所定義的Quirks模式。
  • Almost-Standards Mode(準標準模式)
    支援最新標準,但保有先前版本的圖形渲染行為(Graphic Rendering Behavior)
    要用"-//W3C//DTD HTML 4.0 Transitional//EN"之類的DTD觸發,不太常見,在此略過。

文件模式可透過HTTP Header、HTML <!DOCTYPE>表頭、X-UA-Compatible Metadata或IE開發者工具控制。用IE開發者工具切換的做法大家己經很熟,在此就不多說,這裡只聚焦於「如何調整HTML內容,將IE切換成想要的文件模式」。

MSDN文件 Specifying legacy document modes

HTML可從兩個地方控制文件模式,HTML最前方的<!DOCTYPE>以及X-UA-Compatible Header,而IE套用原則如下:

  • 若網頁同時有<!DOCTYPE>及X-UA-Compatible Header,以Header為準
  • 若瀏覽器支援X-UA-Compatible Header,但不支援Header指定的文件模式,將採用最高文件標準(IE=Edge)
  • 舊版瀏覽器如不支援X-UA-Compatible Header,由<!DOCTYPE>決定
  • IE9(含)以前會以IE5(Quirks) Mode顯示沒有標示<!DOCTYPE>的網頁,建議網頁一律都加<!DOCTYPE html>。

IE會以標準模式處理具有<!DOCTYPE>宣告的網頁,否則就採Quirks模式。X-UA-Compatible Header應放在<header>區前方,其前方只允許存在其他meta Header或<title>。

IE9起,內嵌FrameSet或IFrame時,網頁也只能有一種文件模式,例如:IE9模式網頁以IFrame內嵌Quirks模式網頁,IFrame網頁還是會以標準模式顯示。IE10才做了改良,允許IFrame網頁模擬Quirks模式。

X-UA-Compatible Header有以下寫法:

  1. 限制IE使用IE9/IE8/IE7模準模式
    <meta http-equiv="x-ua-compatible" content="IE=9" >
    <meta http-equiv="x-ua-compatible" content="IE=8" >
    <meta http-equiv="x-ua-compatible" content="IE=7" >
  2. EmulateIE*
    EmulateIE*的效果視<!DOCTYPE>宣告而定,若依宣告使用標準模式,則IE會依EmulateIE*決定使用IE9/8/7標準模式;若網頁缺少<!DOCTYPE>宣告進入Quirks模式,則一律改用IE5模式。
    例如:IE=EmulateIE7用在包含<!DOCTYPE>網頁效果等同IE=7,若網頁沒有<!DOCTYPE>(Quirks模式),效果等同IE=5。
    <meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
    <meta http-equiv="x-ua-compatible" content="IE=EmulateIE8" >
    <meta http-equiv="x-ua-compatible" content="IE=EmulateIE7" >
    補充資料

了解原理,不做實驗驗證感覺不夠踏實。我找到stackoverflow的範例,很巧妙地利用Quirks模式才能識別的樣式以及圓角效果突顯文件模式,出現綠色時表示Quirks模式生效,出現圓角表示為IE9以上的文件模式。另外我還加了段檢測VBScript是否生效的橋段,看到VBScript is alive!代表支援VBScript。為方便測試,我把程式寫成ASPX,透過QueryString參數docType=Y/N決定要不要加<!DOCTYPE html>宣告,參數ie=7/8/9/10/11/Edge/EmulateIE7用來指定X-UA-Compatible Header。另外,網頁也印出navigator.userAgent字串方便觀察IE模式。

完整程式範例如下:

<%@Page Language="C#" %>
<%= Request["docType"]=="Y"?"<!DOCTYPE html>":""%>
 
<html>
<head>
<%= !String.IsNullOrEmpty(Request["ie"])?
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=" + Request["ie"] + "\">" : ""%>
</head>
<body>
<divid="vbTest">VBScript is dead!</div>
<scripttype="text/vbscript">
    If IsNumeric(123) Then 
        document.getElementById("vbTest").InnerText = "VBScript is alive!"
    End If
</script>
<div style="background: #cf0000;background: 007f00;border-radius: 12px;padding: 12px;
          width: 400px; color: yellow">
<ul>
<li>In quirks mode this box is green.</li>
<li>In IE9 mode, it's red.</li>
<li>In quicks mode emulation, it's green but still rounded.</li>
</ul>
</div>
<script type="text/javascript">
  document.write(navigator.userAgent);
</script>
</body>
</html>

實驗結果

以下四組均加入<!DOCTYPE html>,X-UA-Compatible則分別設為IE=Edge, IE=11, IE=10, IE=9。

由於使用IE9以上標準模式,div均呈現紅色園角,IE=Edge/IE=11完全相同,不支援VBScript,userAgent無MSIE版本相容訊息。

IE=10/IE=9時,userAgent出現"comptabile; MSIE 10.0;"以及"comptabile; MSIE 9.0;",代表IE已啟用相容模式。

再來的四組也包含<!DOCTYPE html>,X-UA-Compatible分別設為IE=8, IE=7, IE=EmulateIE7, IE=5。

當文件模式降到IE9以下,圓角不見了,而userAgent有"comptabile; MSIE X.0"。比對IE=Emulate7與IE=7的效果相同,驗證先前所說「IE=EmulateIE7用在包含<!DOCTYPE>網頁效果等同IE=7」。IE=5很慘,div底色完全出不來,不愧是瀏覽器界的山頂洞人。

最後來測試Quirks模式,透過docType=N拿掉<!DOCTYPE html>,如不加X-UA-Compatible,會出現綠底圓角,證實處於Quirks模式。

若加上IE=7,依先前X-UA-Compatible優先於<!DOCTYPE>原則,進入IE7標準模式。

若加上IE=EmulateIE7,依先前理論,Quirks配上EmulateIE7等同IE=5,得到如上圖最後IE=5相同的結果。

搞完這一大串,對IE的文件模式總算進入「略懂」狀態,呼~ Orz

IE11、XML Data Island與VBScript

$
0
0

前文提到眼前的棘手任務是在HTML5網站整合活化石等級的ASP(是的,就是ASP!不是ASPX),其中一大挑戰是ASP網頁裡大量使用XML Data Island與VBScript,能不能與HTML5並存是未知數,心中最理想結果是以IFrame內嵌ASP,大草原上羚羊跟迅猛龍一起快樂奔跑,世界真美好~當然,最後證明這只是不切實際的幻想,真實上演的是迅猛龍趕羚羊咬羚羊的慘烈場景 orz

試著在HTML5 IFrame內嵌實際的ASP,陸續發生詭異錯誤,為讓測試單純化,我簡化成用一個parent.html加一個embedded.html重現問題:

parent.html

<!DOCTYPEhtml>
<html>
<head>
<metahttp-equiv="X-UA-Compatible"content="IE=Edge">
</head>
<body>
<iframesrc="http://portal.lab.com/Blah/embedded.html"></iframe>
</body>
</html>

embedded.html (裡面加進測試實際ASP時出錯的VBScript及XML Data Island寫法)

<!DOCTYPEhtml>
<html>
<head>
<metahttp-equiv="X-UA-Compatible"content="IE=7">
<title>Embedded</title>
<metacharset="utf-8"/>
</head>
<body>
<scripttype="text/javascript">
        alert("JavaScript");
</script>
<script type="text/vbscript">
        MsgBox "VBScript"
        Sub ButtonClick(strText)
            MsgBox strText + " Clicked"
        End Sub
</script>
<buttononclick="BLOCKED SCRIPTcall ButtonClick(Me.InnerText)">Test1</button>
<buttononclick="BLOCKED SCRIPTButtonClick(Me.InnerText)">Test2</button>
<xml>
        This parses as <b>HTML</b> in Internet Explorer 10 and other browsers.
        In older versions of Internet Explorer it parses as XML.
</xml>
</body>
</html>

用IE11獨立瀏覽embedded.html完全正常,靠著X-UA-Compatible IE=7啟用IE7標準模式,XML Data Island、VBScript都活得好好。

當embedded.html被以IFrame內嵌進parent.html時,一切變了樣,VBScript跟XML Data Island都失效了。

查文件確定兩件事:

  1. IE10起不再支援XML Data Island
    https://msdn.microsoft.com/en-us/library/hh801224(v=vs.85).aspx
  2. IE11起不支援VBScript
    https://msdn.microsoft.com/en-us/library/dn384057(v=vs.85).aspx

不支援是指IE11/IE10標準模式不支援,切到IE7/IE8/IE9相容模式,老東西還可以再戰十年。問題出在parent.html用了X-UA-Compatible IE=Edge(代表使用IE最新標準模式),必須改成IE=9以確保XML Data Island跟VBScript可以運作。關掉並重啟IE(註:發現這個修改要重開IE才生效),XML Data Island OK了,VBScript的MsgBox也能執行,但部分VBScript出現錯誤:

  1. onclick="BLOCKED SCRIPTcall ButtonClick()"的call必須移除
  2. IE認不得ButtonClick(Me.InnerText)中的Me(相當於JavaScript的this)

單獨執行embedded.html不會發生這些錯誤,由此推論embedded.html被內嵌在parent.html時與獨立執行時的IE模式不完全相同。在stackoverflow找到一段透過navigator.userAgent偵測IE模式的範例:

<!DOCTYPEhtml>
<html>
<head>
<metahttp-equiv="X-UA-Compatible"content="IE=7">
<title>Embedded</title>
<metacharset="utf-8"/>
</head>
<body>
<script>
//ref: http://stackoverflow.com/a/34928891
function IeVersion() {
var value = {
        IsIE: false, TrueVersion: 0, ActingVersion: 0, CompatibilityMode: false
    };
 
//Try to find the Trident version number
var trident = navigator.userAgent.match(/Trident\/(\d+)/);
if (trident) {
        value.IsIE = true;
//Convert from the Trident version number to the IE version number
        value.TrueVersion = parseInt(trident[1], 10) + 4;
    }
 
//Try to find the MSIE number
var msie = navigator.userAgent.match(/MSIE (\d+)/);
if (msie) {
        value.IsIE = true;
//Find the IE version number from the user agent string
        value.ActingVersion = parseInt(msie[1]);
    } else {
//Must be IE 11 in "edge" mode
        value.ActingVersion = value.TrueVersion;
    }
 
//If we have both a Trident and MSIE version number, see if they're different
if (value.IsIE && value.TrueVersion > 0 && value.ActingVersion > 0) {
//In compatibility mode if the trident number doesn't match up with the MSIE number
        value.CompatibilityMode = value.TrueVersion != value.ActingVersion;
    }
return value;
}
var ie = IeVersion();
document.write("UserAgent: " + navigator.userAgent + "<br />");
document.write("IsIE: " + ie.IsIE + "</br>");
document.write("TrueVersion: " + ie.TrueVersion + "</br>");
document.write("ActingVersion: " + ie.ActingVersion + "</br>");
document.write("CompatibilityMode: " + ie.CompatibilityMode + "</br>");
</script>
</body>
</html>

實測發現,embedded.html明確宣告X-UA-Compatible IE=7,獨立開啟時為IE7模式:

被內嵌於X-UA-Compatible IE=9的parent.html時,embedded.html也處於IE9模式:

這就是MSDN文件所說:

As of IE9 mode, webpages cannot display multiple document modes. For example, consider a standards-based webpage that contains a frame element that displays content in quirks mode. IE9 mode displays the child frame in standards mode (because the parent document is in standards mode). Starting with Internet Explorer 10, however, child frames can emulate quirks mode. For more info, see IEBlog: HTML5 Quirks mode in IE10. For best results, however, use document modes consistently.

IE9起,檢視網頁只允許存在一種文件模式,故IFrame網頁必須沿用父網頁的IE標準模式。IE10做了點改善,允許IFrame網頁啟用Quirks相容,但Quirks相容僅能涵蓋一些舊版樣式、HTML語法,對於XML Data Island、VBScript語法支援問題無能為力。

由實驗印證IE同一時間只能使用一種標準模式,父網頁與IFrame要嘛一起用IE9,要嘛一起用IE7,而IE9與IE7對於VBScript支援有些許差異,除非修改VBScript程式,不然就得降級到IE7。

歸納結論如下:

  1. 如要使用VBScript,IE必須指定IE10以下的IE相容模式
  2. 如要使用XML Data Isaland,則必須使用IE9以下相容模式
  3. 透過IFrame內嵌網頁會沿用父網頁的文件模式,透過X-UA-Compatible亦無法改變
  4. IE7與IE9都支援VBScript,但在語法與部分細節上有所差異,必須修改後才能使用

評估之後,修改老程式無可避免,一些網頁只能認命重寫~順勢消滅一些IE Only老網頁,就當做功德吧!orz

【邏輯來找碴】燕麥有農藥好可怕?

$
0
0

這兩天關於大燕麥有則大消息:

衛福部食品藥物管理署抽驗市售36件燕麥片產品,有10件燕麥片驗出含有年年春,含量為0.1ppm(百萬分之一)到1.8ppm。食藥署要求違規業者應在收到下架通知的72小時內完成下架。

頓時社會一片嘩然,媒體大幅報導(查了一下,Google新聞有101則呢),大家爭相走告,群情恐慌。身為大燕麥重度食用者,我也收到好多來自同事朋友的關心,但在了解始末之後,我只想說「茄!這不科學啊!這是那門子鬼檢驗?大家在恐慌什麼啦?」

先來看食藥署的檢驗標準為「不得檢出」,而本次不合格產品的含量為0.1ppm到1.8ppm,變成大家口中的「有毒」!但依據毒物專家的看法,1.8ppm代表60公斤成人一天要吃超過10公斤才有中毒風險,一天吃掉10公斤燕麥?你是牛嗎?

既然連1.8ppm的風險都不算高,為什麼台灣要訂出零檢出的超嚴格標準呢?故事就更有趣了。食藥署表示,因為台灣沒有種植燕麥,因此國內並未針對燕麥中殘留的嘉磷塞訂出限量標準,因為沒有標準,因此就以「不得檢出」作為管理基準;但依國外規範。美國與國際食品法典委員會(CODEX)的標準是小於30ppm,歐盟與日本則是不得超過20ppm。而進口黃豆有訂嘉磷塞殘留容許量標準,上限為10ppm,毛豆則是0.2ppm。

WTF!

意思是食藥署從沒研究過燕麥殘留嘉磷塞的合理上限,拿了一個莫名其妙標準去檢驗並要求下架。依資料庫程式開發者的角度,目前的上限值是NULL,不是零啊啊啊~在SQL WHERE條件拿任何數值跟NULL比大小,結果永遠是NULL,不會是True或False,哪來的不合格?何需恐慌?

2016海山馬

$
0
0

唯一留下落馬記錄的海山馬,對我有不一樣的意義,雖然去年已雪恥,但麥克阿瑟將軍說過「Pick yourself up from where you’ve fallen… at least twice.」「從哪裡跌倒,就從哪裡爬起來... 至少兩次!」(麥克阿瑟:拎杯何時說過這種鬼話?)為此,我毫不猶豫地又報了名。

早早來到會場,天氣多雲微雨,氣溫22-27,老天爺再次賞了個不錯的好天氣。

出發前在橋下看到志工同學的機車車隊正要出發到各水站。

全馬參賽人數只有七百多人,是我偏愛的小而美賽事。

參加這種小比賽的女生實力都很強大,拍完照片我就被海放惹,最後10K再遇時估計已落後快5公里 Orz

1.5小時跑到南端折返點中正橋,已完成14K,速度還可以。

順便打小報告,照片裡有某位葛格亂丟塑膠杯破壞環境,小朋友不要學。

橋下吊著一隻塑膠翼手龍,配上恐龍追自行車手的壁畫,挺有趣。

今年的補給比較平淡,都是水、運動飲料、西瓜… 等。在光復橋路過私補站,被熱情招呼,喝到好喝的仙草,感謝。(忘了問是哪個單位,看衣服顏色猜是永慢的大哥大姐,如猜錯請恕罪)

沒本事拼成績破PB,邊跑邊玩,附庸風雅亂拍幾張也好。

在浮洲濕地一帶看到很美的荷花。

再次回到落馬傷心地,去年圍起工程圍籬正準備興建「黑大落馬紀念碑」,世事難料,因逃生路線規劃引起爭議,市府摃上包商,最新發展甚至可能會走向合意解約,可惜了…

跑馬也能學習前端新知-JSONE,JavaScript Object Notation Extension,為原有JSON格式的強化與擴充,只需10KB空間即可儲存人類完整基因序列資料,其編碼演算法至今成謎,據信與外星科技有關。以上純屬唬爛,查了資料,JS ONE似乎是某個神祕塗鴉團體或作者使用的署名。

在遠方山頭看到漂亮的漸層。

浮洲橋往三峽有一段經過不知名的樹開滿了細碎白花,風一吹來便落下陣陣花雨,慢跑其中,好不浪漫… 一點也不好嗎?實際上我一路上忙著「呸呸呸」吐掉跑進嘴巴的花絮,只能頭低低呼吸放輕快速通過。

北側折返點哨所站到了,花了4小時又5分鐘,剩下最後7K,此時涼風陣陣十分舒爽,決定拼個SUB5,不要辜負老天爺給的好天氣。

連滾帶爬保住SUB5,為上半年的跑馬行程劃下逗號,等天涼再戰~

萬年不變的公版完賽獎牌,但沒印全馬半馬讓人有點小失望。

SQL效能調校經驗一則

$
0
0

使用者報案,某網頁效能變得奇慢無比,簡單的上線公告查詢耗時超過兩分鐘,追查後抓出問題查詢如下例:

select
casewhen
convert(varchar,u.StartDate,108)='00:00:00'and
convert(varchar,u.EndDate,108)='00:00:00'
thenconvert(datetime,convert(varchar,u.EndDate,111))+1 
else EndDate endas EndDate
,u.SomeNTextCol
,u.MoreCols
,u.Priority
,dbo.ufn_GetDisplayText('Priority',u.Priority) as PriorityName,
c.CateId
from Post u join Category c on u.UID=c.uid

查詢結果共4939筆,總資料約40MB,耗時卻達80秒以上,查詢緩慢現象用SSMS手動查詢亦可重現。原本懷疑卡在User Defined Function,排除後速度依舊。最後對照測找出NTEXT欄位是關鍵,資料表Post有多個NTEXT欄位,只要SELECT包含任何NTEXT欄位,速度就會慢到一分鐘以上;若完全不加NTEXT欄位,3秒內解決。

依我的理解,NTEXT欄位效率不如NVARCHAR(MAX),但差異誇張若此並不合理。檢視伺服器狀況,發現疑點:

全部4GB記憶體耗用超過95%,堂堂SQL Server竟只有不到300MB記憶體可用,且CPU%偏高,而Reporting Service吃掉了近600MB記憶體。莫非效能問題是記憶體不足引起?

使用Set Statistics IO ON取得IO統計數據,推測獲得證實:

(4939 row(s) affected)
Table 'Category'. Scan count 1, logical reads 21, physical reads 17, read-ahead reads 17, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Post'. Scan count 1, logical reads 325, physical reads 320, read-ahead reads 318, lob logical reads 82618, lob physical reads 10819, lob read-ahead reads 1333.

查詢NTEXT欄位涉及LOB讀取,於本例產生82618個讀取數,其中10819依賴實體磁碟讀取,反覆執行也不會下降,代表SQL Server無法藉由Cache提升效能,足以推論記憶體不足是效能瓶頸。

重啟Reporting Service釋放記憶體,SQL Server記憶體量上升到460MB,查詢速度瞬間加速到3秒以內。

(4939 row(s) affected)
Table 'Category'. Scan count 1, logical reads 23, physical reads 1, read-ahead reads 22, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Post'. Scan count 1, logical reads 325, physical reads 0, read-ahead reads 0, lob logical reads 82618, lob physical reads 0, lob read-ahead reads 1333.

由IO統計,實體讀取數由10819降至0,意味著所有資料均由Cache取得,是速度提升主因。

所以這台4GB的SQL主機記憶體不足要加RAM?不然,SQL Server只用了不到300MB,嚴格來說,不是記憶體不足,而是SQL搶不到記憶體。由Task Manager的清單,所有Process耗用的記憶體數量加總不到2G,還有近2G到哪裡去了?想知道Windows記憶體用到哪裡去,RAMMap是絕佳工具:

答案揭曉,Process Private總計約1.8GB,與Task Manager各Process用量加總相近。而Mapped File與Metafile為Windows檔案Cache,佔用近1GB,理論上應讓出空間給SQL使用以改善效能。該SQL總資料量不足500MB,屬於小型資料庫(這也是機器只配4G記憶體的理由),最後決定將SQL起始最小記憶體數設為512MB起跳,避免未來再發生SQL搶輸記憶體的場面。

留下一則疑惑:我們都知道Windows會積極透過檔案Cache提升效能,但在記憶體不足時,Windows難道不會主動釋放Cache舒緩記憶體吃緊狀況?

會這樣想是因為ASP.NET就有這種聰明設計:參考

Cache 類別會提供強大的功能,讓您自訂快取項目的方式和快取的時間長短。例如,當系統記憶體不足時,快取會自動移除不常使用或低優先權的項目,以釋放記憶體。這個技術稱為清除 (Scavenging),也是快取可確定過時資料不會耗用寶貴的伺服器資源的其中一種方法。

這是我心中理想的記憶體管理策略,但依本案例與先前經驗,Windows的檔案Cache策略並不如.NET Cache有彈性。關於Windows會不會釋出檔案Cache解決記憶體不足危機,我一直沒找到明確官方文件證實,倒是爬文找到一篇相關文章

If you’re on Windows Server 2008 or newer, you should also make sure that the Windows file system cache isn’t eating up all of your memory. Windows will cache data for file system access when you drag and drop a file, copy it with xcopy, or push a backup across the network. This behavior is by design because it’s supposed to add to the feeling of faster performance for the end user. Unfortunately, it also steals RAM from server-side processes like SQL Server.
Windows 2008起會積極使用檔案Cache改善使用者的操作經驗,但顯然這會排擠SQL之類伺服器所能使用的記憶體資源。

然而,這次遇上的應屬少見案例。一般資料量大且忙碌的SQL Server,啟動後很快就會建立大量Cache,不致空留記憶體給Windows檔案Cache蠶食。這台SQL資料量很少,但會以緩慢速度增加,時間一久才會發生記憶體搶輸Windows檔案Cache的結果,面對這種狀況,保留基本記憶體量可視為有效解決方案,結案。

【茶包射手日記】TypeScript MSBuild編譯失敗

$
0
0

接獲報案,專案修改無法使用TFS Build Service編譯,錯誤訊息如下:

D:\Works\1\BLAH\src\Web\Scripts\Foo.ts (112): Build: 'let' declarations are only available when targeting ECMAScript 6 and higher.

訊息指向程式使用TypeScript新支援的let語法,只能編譯成ES6,但專案設定輸出ES5。但專案在本機Visual Studio + TypeScript 1.8編譯正常,TFS Build Service使用MSBuild編譯才出錯。

優先懷疑問題出在TFS Build Service主機的TypeScript未更新,依據官方文件,TypeScript 1.8起才支援在ES3及ES5時使用let及const,確認為版本過舊引起:

let and const support
ES6 let and const declarations are now supported when targeting ES3 and ES5.

檢查\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v12.0\TypeScript\TypeScript.Tasks.dll為v12.18.50.50301,已是最新版。(註:我們的TFS Build Service使用VS2013 MSBuild,故為v12;若使用VS2015,則為v14)

回頭檢查MSBuild編輯失敗Log,找到線索:

2>PreComputeCompileTypeScript:
C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.4\tsc.exe --sourcemap --target ES5 --noEmitOnError COMPUTE_PATHS_ONLY "D:\Works\1\BLAH\src\Web\Scripts\Foo.ts" "D:\Works\1\BLAH\src\Web\Scripts\Bar.ts" "D:\Works\1\…

以此研判,MSBuild仍使用1.4版TypeScript Compiler編譯程式,導致無法在ES5使用let。檢查csproj檔案,發現問題所在:

<ProjectToolsVersion="4.0"DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportProject="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
\TypeScript\Microsoft.TypeScript.Default.props"
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
\TypeScript\Microsoft.TypeScript.Default.props')"/>
<ImportProject="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
<PropertyGroup>
<ConfigurationCondition=" '$(Configuration)' == '' ">Debug</Configuration>
<PlatformCondition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<!-- 省略 -->
<SccLocalPath>SAK</SccLocalPath>
<SccAuxPath>SAK</SccAuxPath>
<SccProvider>SAK</SccProvider>
<TargetFrameworkProfile/>
<TypeScriptToolsVersion>1.4</TypeScriptToolsVersion>
<MvcProjectUpgradeChecked>true</MvcProjectUpgradeChecked>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>4.0</OldToolsVersion>
</PropertyGroup>

其中<TypeScriptToolsVersion>1.4</TypeScriptToolsVersion>不知何故還停在1.4未改成1.8,但顯然在Visual Studio編譯時會忽略此設定,仍以1.8編譯。

手動修改csproj將版號改為1.8,問題排除,收隊。

學到三件事:

  • MSBuild的TypeScript Task設定放在 \Program Files (x86)\MSBuild\Microsoft\VisualStudio\v12.0\TypeScript\
  • TypeScript Compiler(tsc.exe)則在\Program Files (x86)\Microsoft SDKs\TypeScript\1.x
  • MSBuild時會依csproj裡的<TypeScriptToolsVersion>決定tsc.exe版本,與Visual Studio編譯使用版本不一定相同

【獅子鬃毛】臉書官方承認竊聽使用者日常談話?

$
0
0

又在報紙看到聳動標題「臉書驚爆竊聽門 官方承認用APP蒐集用戶隱私」,消息來自一篇標題更聳動的Blog文章「臉書版竊聽風雲 Facebook承認使用手機竊聽使用者日常對話(內有關閉方式)」。試圖蒐集使用者資料或有可能,但官方大方承認「沒錯,拎杯就是要偷聽」無疑是自殺行為,有失常理。這一點讓我心中的「不合邏輯」警報大作,決定追根究底一番。

電腦王阿達Blog引用的是Independent這家媒體的報導,發佈時間為6/2,內容提到南佛羅里達大學教授Kelli Burns實驗發現臉書會依據她在手機旁的談話決定推薦貼文及廣告,但同一篇報導有提到Facebook發言人的否認聲明:Facebook可透過麥克風聆聽聲音也會蒐集使用者資訊,但二者並沒有結合,不會依據聽到內容決定App要顯示什麼。發言人告訴Independent「Facebook絕對不會使用麥克風錄音決定廣告或訊息。廣告可能依據使用者的興趣或人口統計分析推送,但不會透過音訊蒐集」。

Facebook said that it does listen to audio and collect information from users – but that the two aren't combined, and that sounds heard around people aren't used to decide what appears in the app.

“Facebook does not use microphone audio to inform advertising or News Feed stories in any way," a spokesperson told The Independent. "Businesses are able to serve relevant ads based on people’s interests and other demographic information, but not through audio collection.”

而Facebook說明頁亦有關於使用麥克風蒐集資料的說明

    • Facebook 何時會開啟我的麥克風,嘗試識別我正在聆聽或正在觀看的內容?

      如果您選擇允許我們識別您正在聆聽或正在觀看的內容,我們才會在您每次撰寫近況更新時使用您的麥克風。當我們找到相符內容,或是在短時間內無法找到相符內容時,我們都會停止使用您的麥克風。
      注意:此功能目前僅在美國可以使用。
    • Facebook 在識別我正在聆聽或正在觀看的內容時,是否會記錄對話?

      否,我們不會記錄您的對話。當您撰寫近況更新時,我們只會使用您的麥克風,根據我們能找出的音樂和電視相符內容,識別您正在聆聽或正在觀看的內容。
      注意:此功能目前僅在美國可以使用。

換句話說,Facebook自始至終嚴正否認,何來官方承認之說?

至此應可推論「Facebook承認使用手機竊聽使用者日常對話」一說並無根據,甚至與其引用報導內容相反,大家不用驚慌。

追查過程找到更多Kelli Burns指控Facebook偷聽的有趣資料,其中最詳細的是NBC於5/24做了一則新聞報導(有影片),還訪問了Burns教授本人。報導說Burns實測在手機旁講完「想去非洲Safari草原,如果能坐吉普車一定很好玩」,60秒後Facebook塗鴉牆第一則消息就跟Safari相關,而廣告也出現吉普車租車廣告,她認為「不可能這麼巧,一定有鬼」。但影片並非當場實測,似乎也缺乏能反覆驗證的科學證據,很有可能只是Burns依據單次經驗做出推論… Orz 容我引用「思考的藝術」案例:

奇蹟
十五個唱詩班成員因不同的原因遲到躲過教會爆炸,被認定是神蹟。
AMD與Intel公司的同名員工同一天入住加州一家飯店,飯店人員將機密文件交錯人,引爆一起專利案。
看似奇蹟的事,其出現的機率可被計算而得,並非不可能發生。

由此,無法排除整起偷聽疑雲只是一椿「非受迫性思考錯誤」 :P

討厭生活被謠言圍繞,就算知道寫這種文章是狗吠火車,難敵聳動標題,「臉書會偷聽你聊天」最終還是會在街頭巷尾流傳,甚至會有親朋友好心提醒(寫這篇用意之一也在方便直接貼連結回應,呵),就當成邏輯控的一點努力吧~

EntityFramework SaveChanges時出現OriginalValues取值錯誤

$
0
0

使用Entity Framework新増資料,SaveChanges()時出現以下錯誤:

System.Data.Entity.Validation.DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
   於 System.Data.Entity.Internal.InternalContext.SaveChanges()
   於 System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
   於 System.Data.Entity.DbContext.SaveChanges()

*** DbEntityValidationException.EntityValidationErrors ***
Failed to dump exception: Error getting value from 'OriginalValues' on 'System.Data.Entity.Infrastructure.DbEntityEntry'.

深入檢查發現,要寫入的資料表有個不允許NULL的VARCHAR欄位,為新増資料物件對應該欄位的字串屬性忘了給值(呈現null)引發,修正後錯誤消失。原因很單純問題不難解,只是錯誤訊息玄疑了點。有此經驗,下回看到DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details. 別看 EntityValidationErrors 以免愈看愈花,仔細檢查有沒有欄位少給值、長度不符… 就對了。

感謝ace網友提醒,本起事件是一起烏龍。上述錯誤訊息是由自製函數產生,其中"Failed to dump exception"已提示為印出輸出例外資訊時出錯,Error getting value from 'OriginalValues' 並非EntityValidationErrors內容,一時失察錯判問題來源,先向大家致歉,晚點再提供更多細節。

【水電工日記】瓦斯爐點不著,鬆手就熄火

$
0
0

聲明在先,瓦斯爐維護一定有風險,大家請審慎衡量,若無把握或不想冒險,建議請專業師傅處理較為妥當。本文純為經驗分享,恕不負任何責任。

家裡的瓦斯爐有一爐打火困難,就是傳說中「旋鈕點火,一鬆手火就滅」的常見問題,近來情況日益惡化,壓著有火,鬆手就滅,一搞三五分鐘,水都快燒開了還點不著。

先說說「一鬆手火就滅」的原因。近代瓦斯爐基於安全考量,都有加裝熄火自動切瓦斯的安全裝置,原理是在爐嘴安裝一枝探針型「熱電偶」連到控制瓦斯輸出與否的「電磁閥」,熱電偶受火熖加熱會輸出電壓,電壓驅動電磁閥開啟,供應瓦斯持續燃燒。若是爐火被湯燒熄或被風吹熄,熱電偶不再受熱失去電壓,電磁閥便會主動切斷瓦斯,避開瓦斯外洩釀災。這也是為什麼點火要先壓住一陣子的理由:手動強制輸出瓦斯先將熱電偶燒熱,待其產生足夠電壓觸發電磁閥待續開啟供應瓦斯,自此才可鬆手宣告點火完成。

由此推測,一鬆手火就熄的源頭有二:熱電偶受熱時未輸出足夠的電壓,再不然就是電磁閥故障。網路教一些簡單的故障排除技巧,例如:清除熱電偶表面鏽蝕、檢查熱電偶接線…。先前用刀片刮過一次熱電偶尖端,曾好了一陣子,最近再急速惡化到完全開不起來,決定拆下來檢查,順便確定規格採購零件。

瓦斯爐使用的熱電偶規格不一,主要差別在於長度跟接頭(上圖藍標所指處,我家這台是有卡榫的,拆卸時要用尖嘴鉗夾一下;另外有看到一種為單純圓柱狀母插頭,各家瓦斯爐不同),綠標所指為地線接頭,黃標所指處為固定螺絲。

Mobile01有完整拆解教學文,黃標固定螺絲處的活動空間有限,完全拆解較好施工,但我不想動到瓦斯管路,便找了蕀輪扳手單拆固定螺絲。取下熱電偶後用銼刀將感應端打磨抛光,接三用電錶測試,以打火機加熱最高可以產生13mV,看來沒壞。

既然測試正常就重裝回去,點火功就這麼正常了,無法斷定是刮痧除鏽抛光的功勞(除鏽抛光前忘了測電壓對照,扼腕)還是重裝線路排除問題,不想深究問題根源,能燒飯就好,就醬!

還是上網買了一組備品如下圖,測過新品,打火機加熱產生的電壓數字差不多,零件入倉列管,留待下回故障上場。

ASP網頁IE9相容拉皮筆記

$
0
0

整合清代ASP的工作持續進行,上回提過用IFrame內嵌ASP網頁時,父網頁與IFrame必須為同樣的IE模式,若父網頁要採HTML5時代寫法,就得考量各程式庫的IE最低版本要求,例如:KendoUI為IE8以上、AngularJS 1.3+需要IE9,再加上XML Data Island最後的支援版本為IE9、VBScript只到IE10,算下來IE9是基本要求。

初試幾個老ASP網頁切到IE9大致可行,但要改的小地方挺多,這篇先整理從IE5拉到IE9標準遇到的幾個CSS問題。

我把狀況整理成一個問題大全:

<html>
<head>
<metahttp-equiv="x-ua-compatible"content="IE=5">
<style>
        body {
            background-image: url(".\imgs\background.png");
        }
        table {
            width: 320;
            margin: 6px;
            background-color: 444444;
        }
        td {
            color: orange;
            padding: 6px;
        }
</style>
</head>
<body>
<divstyle="text-align: center;">
<tableborder="1">
<tr><td>標題</td><td>作者</td></tr>
<tr><td>黃鶴樓送孟浩然之廣陵</td><td>李白</td></tr>
<tr><td>楓橋夜泊</td><td>張繼</td></tr>
</table>
</div>
</body>
</html>

它在IE5模式本來長這様:

一旦切成IE9模式立刻面目全非:

整理上述網頁CSS寫法問題如下:

  1. 背景圖不見
    原本寫法 background-image: url(".\imgs\background.png") 用了倒斜線,需修正為斜線
  2. 表格未置中
    父容器的text-align: centert寫法對<table>無效,改成設定width再用margin: 6px auto置中
  3. 表格欄位繼承置中
    在IE7之後,<div>的text-align: center會繼承到<td>,需另設text-align:left拉回來
  4. 表格寬度不對
    width: 320少寫了"px",古代會自動補上px,現代則視為無效尺寸
  5. 表格背景色無效
    IE5(Quirks)規格允許#rrggbb色碼省略"#",要走新標準得補回

修改結果如下:

<!DOCTYPEhtml>
 
<html>
<head>
<metahttp-equiv="x-ua-compatible"content="IE=Edge">
<style>
        body {
            background-image: url("./imgs/background.png");
        }
        table {
            width: 320px;
            margin: 6px auto;
            background-color: #444444;
        }
        td {
            text-align: left;
            border-color: white;
            color: orange;
            padding: 6px;
        }
</style>
</head>
<body>
<divstyle="text-align: center;">
<tableborder="1">
<tr><td>標題</td><td>作者</td></tr>
<tr><td>黃鶴樓送孟浩然之廣陵</td><td>李白</td></tr>
<tr><td>楓橋夜泊</td><td>張繼</td></tr>
</table>
</div>
</body>
</html>

修改後,除了邊界位置有些出入,在IE9/IE10/IE11均可正常顯示,拉皮成功!

淺談IFrame式Clickjacking攻擊與防護

$
0
0

用IFrame內嵌其他網頁是很常見的整合技巧,兩個獨立開發網頁可分別使用,有需要再合體,被內嵌的網頁配合移除頁首頁尾只留內容,看起來天衣無縫,省事又方便。

大家猜猜以下HTML寫法會有什麼結果,網頁上有一小塊出現Google首頁?

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width">
<title>IFrame Example</title>
<style>
    iframe { width: 480px; height: 300px; }
</style>
</head>
<body>
<iframesrc="https://www.google.com.tw"/>
</body>
</html>

錯了!

用Chrome檢視上述網頁(Live Demo),IFrame一片空白:

改用IE或Edge開啟得到的訊息多一點:

Edge的錯誤訊息解釋了無法顯示Google首頁的原因:

此內容無法在框架中顯示
這裡應該要有一些內容,但發行者不允許它在框架中顯示。這是為了協助保護您可能在此網站輸入任何資訊的安全性。

解決之道是不要用IFrame內嵌,另開新視窗即可正常檢視。

透過Chrome F12工具我們可以獲得技術面的解釋,有個錯誤訊息:Refused to display 'httqs://www.google.com.tw/' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'. 檢查Response Header,的確可以看到x-frame-options: SAMEORIGIN。這是瀏覽器的安全防護特性之一,限制符合同源政策的網頁才能用IFrame、Frame或Object內嵌這個網頁。

防止網頁被內嵌目的在防止IFrame式Clickjacking攻擊(註:點擊刧持還有其他形式,本文只聚焦透過IFrame攻擊的手法),避免惡意網頁將你的網頁疊加在一般按鈕或連結上方誘使使用者點擊,不知不覺完成開放權限、身份確認… 等操作。隨便調查一下,發現不只Google,像Facebook、Twitter、Yahoo這些大網站也紛紛在HTTP Header加入X-Frame-Options: DENY或SAMEORIGIN。

由X-Frame-Options已被普遍主流網站採用的現象,若用內容農場下標風格,我們可以說:

Google、Facebook都偷偷在HTTP Header加了這個,背後的原因網站開發者們一定要知道… (謎:你夠了哦)

既然大網站都加了防護,想必此一資安風險不容忽視。但IFrame式Clickjacking真有這麼可怕,怎樣可以透過IFrame騙取使用者點擊?實際來個範例比較好懂。假設有一網頁如下,目的在偵測使用者真實身分,唯有正港豬頭才會按鈕自婊。:

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8"/>
<style>
        body {
            background-color: #0094ff;
        }
        button {
            background-color: red;
            color: yellow;
        }
</style>
</head>
<body>
<buttononclick="alert('我是豬頭')">我是豬頭</button>
</body>
</html>

某駭客黑大發現此網頁未加X-Frame-Options: DENY或SAMEORIGIN防護,心懷不軌搞了個陷阱網頁:先用IFrame內嵌豬頭偵測網頁,利用CSS技巧將IFrame設成position: absolute並調整位置,將「我是豬頭」按鈕蓋在「我是帥哥」按鈕的正上方,再調整CSS opacity透明度使之完全隱形(可參考影片裡的動畫示意)。

使用者只看到「我是帥哥」,按下去一秒變豬頭:

看完示範,應該不難理解為什麼網站要加上X-Frame-Options防護。如果希望整個網站都不准其他網頁內嵌,可以設定網站伺服器在Header中預設加入X-Frame-Options。以IIS為例,可在HTTP回應標頭加入設定:

或直接修改web.config:

<system.webServer>
<httpProtocol>
<customHeaders>
<addname="X-Frame-Options"value="SAMEORIGIN"/>
</customHeaders>
</httpProtocol>
</system.webServer>

註:MDN網頁另有Apache、nginx設定回應標頭的方式可供參考。

不過,這個X-Frame-Options Header需要瀏覽器配合才有防護效果,萬一使用者活在世外桃源,使用的版本太舊(對,還在用IE6/IE7的無懷氏之民與葛天氏之民,我就是在說你們),這招就會破功。若要求保險,網頁可再加上:參考

<head>
<style> body { display : none;} </style>
</head>
<body>
<script>
if (self == top) {
var theBody = document.getElementsByTagName('body')[0];
    theBody.style.display = "block"
  } else { 
    top.location = self.location 
  }
</script>
...
</body>

最後補充一點:針對Cross Site Scripting這類攻擊,網頁安全有個新規格-CSP(Content Security Policy),其中定義了 frame-ancestors可取代 X-Frame-Options,但因為很多瀏覽器還不支援,現階段要防範網頁被內嵌仍是以 X-Frame-Options 為主。

【延伸閱讀】

Viewing all 2311 articles
Browse latest View live