25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
6.1KB

  1. <?xml version="1.0"?>
  2. <?component error="true" debug="false"?>
  3. <component>
  4. <registration
  5. description="WscMvc Application bootstrap"
  6. progid="WscMvc.Application"
  7. version="1.00"
  8. classid="{851C7763-1638-42FE-A166-BF3DD3A96A88}">
  9. </registration>
  10. <public>
  11. <method name="Run">
  12. <parameter name="ctx"/>
  13. <parameter name="statusLine"/>
  14. <parameter name="contentType"/>
  15. <parameter name="body"/>
  16. </method>
  17. </public>
  18. <script language="VBScript">
  19. <![CDATA[
  20. Option Explicit
  21. ' Central request boundary: one place decides expected (404) vs unexpected
  22. ' (COM/method failure -> 500) outcomes, and one place logs them. ctx is our
  23. ' own WscMvc.RequestContext object (not an ASP intrinsic), carrying only
  24. ' primitive request data. No ASP intrinsics are referenced here.
  25. Sub Run(ctx, statusLine, contentType, body)
  26. Dim ctrl, helloBody, path
  27. path = ctx.Path
  28. If path = "/hello" Then
  29. Set ctrl = Nothing
  30. On Error Resume Next
  31. Set ctrl = CreateObject("WscMvc.HomeController")
  32. If Err.Number <> 0 Then
  33. Err.Clear
  34. On Error Goto 0
  35. statusLine = "500 Internal Server Error"
  36. contentType = "text/plain; charset=utf-8"
  37. body = "Internal Server Error"
  38. LogOutcome ctx, statusLine
  39. Exit Sub
  40. End If
  41. On Error Goto 0
  42. helloBody = ""
  43. On Error Resume Next
  44. ctrl.Hello helloBody
  45. If Err.Number <> 0 Then
  46. Err.Clear
  47. On Error Goto 0
  48. statusLine = "500 Internal Server Error"
  49. contentType = "text/plain; charset=utf-8"
  50. body = "Internal Server Error"
  51. Set ctrl = Nothing
  52. LogOutcome ctx, statusLine
  53. Exit Sub
  54. End If
  55. On Error Goto 0
  56. statusLine = "200 OK"
  57. contentType = "text/html; charset=utf-8"
  58. body = helloBody
  59. Set ctrl = Nothing
  60. Else
  61. ' Expected outcome, not a failure: no matching route yet (M3 adds a real table).
  62. statusLine = "404 Not Found"
  63. contentType = "text/plain; charset=utf-8"
  64. body = "Not Found"
  65. End If
  66. LogOutcome ctx, statusLine
  67. End Sub
  68. ' Best-effort diagnostics only: a logging failure must never affect the
  69. ' response. Every filesystem step is individually guarded so one bad
  70. ' operation (e.g. concurrent-write contention) just skips this line rather
  71. ' than raising to the caller.
  72. Sub LogOutcome(ctx, statusLine)
  73. Dim fso, logFile, logPath, lockPath, line
  74. Set fso = Nothing
  75. On Error Resume Next
  76. Set fso = CreateObject("Scripting.FileSystemObject")
  77. If Err.Number <> 0 Then
  78. Err.Clear
  79. On Error Goto 0
  80. Exit Sub
  81. End If
  82. On Error Goto 0
  83. On Error Resume Next
  84. If Not fso.FolderExists(ctx.LogDir) Then
  85. fso.CreateFolder ctx.LogDir
  86. End If
  87. If Err.Number <> 0 Then
  88. Err.Clear
  89. On Error Goto 0
  90. Set fso = Nothing
  91. Exit Sub
  92. End If
  93. On Error Goto 0
  94. logPath = ctx.LogDir & "\app.log"
  95. lockPath = ctx.LogDir & "\app.log.lock"
  96. ' Concurrent requests race for this one shared file. Verified experimentally
  97. ' that FileSystemObject.OpenTextFile(ForAppending) mostly SUCCEEDS for every
  98. ' concurrent caller (it is not exclusive) but their writes overwrite each
  99. ' other - a lost-update race, not an open failure - so retrying the open
  100. ' alone did not help. No ASP intrinsic (Application.Lock/UnLock) is available
  101. ' here by contract, so a manual lock-file mutex is used instead:
  102. ' CreateTextFile(path, OverwriteExisting:=False) atomically fails with
  103. ' Err.Number=58 "File already exists" when another request holds the lock -
  104. ' confirmed correct/exclusive experimentally, not assumed.
  105. '
  106. ' The retry budget below is deliberately short. Measured under 8 genuinely
  107. ' concurrent requests: the mutex itself is correct, but making every request
  108. ' reliably win eventually needs a retry budget of 300+ attempts (~300-360ms
  109. ' of blocking) - too much added latency for a best-effort diagnostic write.
  110. ' With this short budget, heavy concurrent bursts will legitimately drop
  111. ' some log lines rather than delay the response; this is an accepted
  112. ' tradeoff for "best-effort diagnostics" (see docs/DECISIONS.md), not a
  113. ' correctness bug - the HTTP response itself is unaffected either way.
  114. ' Revisit with a real logging mechanism (e.g. per-request files aggregated
  115. ' out of band) in M6 if complete log coverage under load becomes a
  116. ' requirement.
  117. If Not AcquireLogLock(fso, lockPath) Then
  118. Set fso = Nothing
  119. Exit Sub
  120. End If
  121. Set logFile = Nothing
  122. On Error Resume Next
  123. Set logFile = fso.OpenTextFile(logPath, 8, True) ' 8 = ForAppending, create if missing
  124. If Err.Number <> 0 Then
  125. Err.Clear
  126. On Error Goto 0
  127. ReleaseLogLock fso, lockPath
  128. Set fso = Nothing
  129. Exit Sub
  130. End If
  131. On Error Goto 0
  132. line = Now & " | " & ctx.CorrelationId & " | " & ctx.HttpMethod & _
  133. " | " & ctx.Path & " | " & statusLine & " | " & ctx.ElapsedMs() & "ms"
  134. On Error Resume Next
  135. logFile.WriteLine line
  136. Err.Clear
  137. On Error Goto 0
  138. On Error Resume Next
  139. logFile.Close
  140. Err.Clear
  141. On Error Goto 0
  142. Set logFile = Nothing
  143. ReleaseLogLock fso, lockPath
  144. Set fso = Nothing
  145. End Sub
  146. Function AcquireLogLock(fso, lockPath)
  147. Dim attempt, maxAttempts, lockFile, busy, spinCount
  148. maxAttempts = 10
  149. spinCount = 8000
  150. AcquireLogLock = False
  151. For attempt = 1 To maxAttempts
  152. Set lockFile = Nothing
  153. On Error Resume Next
  154. Set lockFile = fso.CreateTextFile(lockPath, False) ' fails if lockPath already exists
  155. If Err.Number = 0 Then
  156. On Error Goto 0
  157. lockFile.Close
  158. Set lockFile = Nothing
  159. AcquireLogLock = True
  160. Exit For
  161. End If
  162. Err.Clear
  163. On Error Goto 0
  164. For busy = 1 To spinCount : Next
  165. Next
  166. End Function
  167. Sub ReleaseLogLock(fso, lockPath)
  168. On Error Resume Next
  169. fso.DeleteFile lockPath, True
  170. Err.Clear
  171. On Error Goto 0
  172. End Sub
  173. ]]>
  174. </script>
  175. </component>

Powered by TurnKey Linux.