1 (изменено: Phoenixxx_Czar, 2021-05-07 08:56:13)

Тема: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Пытаюсь сделать для себя что-то подобие Fetch у JS, и проблема возникла с "мультипоточным" запросом.
Информация о нем: https://www.autohotkey.com/boards/viewt … 247#p34247.

На хостинг приходят запросы, но "User-Agent" не меняется, с "WinHttp.WinHttpRequest.5.1" такого нет.
Лог с сервера:

::1 - - [07/May/2021:01:32:58 +0000] "GET /ahk HTTP/1.1" 200 425 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)"

Вот мой класс (на сколько я помню, там нет доп функций кроме библиотеки JSON):

class Fetch
{
	__New(resource, init := "", callback := "")
	{
		if (init == "")
		{
			init := {}
		}

		this.resource     := resource
		this.method       := (init.method ? init.method : "GET")
		this.headers      := (init.headers ? init.headers : {})
		this.applyHeaedrs := {}
		this.body         := (init.body ? init.body : "")
		this.callback     := callback
		this.isSend       := 0

		if (this.body)
		{
			json := JSON.Stringify(this.body)

			if (strLen(json) > 2)
			{
				this.body := json
			}
		}

		if (!callback)
		{
			this.Request := ComObjCreate("WinHttp.WinHttpRequest.5.1")
			this.Request.Open(this.method, this.resource, false)
		}
		else
		{
			this.Request := ComObjCreate("Msxml2.XMLHTTP")
			this.Request.Open(this.method, this.resource, true)
			this.Request.onreadystatechange := ObjBindMethod(this, "onReadyStateChange")
		}

		for headerName, headerValue in this.headers
		{
			this.setHeader(headerName, headerValue)
		}

		this.defaultHeaders := {}
		this.defaultHeaders["User-Agent"]        := "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36 u01-05"
		this.defaultHeaders["Referer"]           := this.resource
		this.defaultHeaders["Content-Type"]      := "application/x-www-form-urlencoded"
		this.defaultHeaders["Pragma"]            := "no-cache"
		this.defaultHeaders["Cache-Control"]     := "no-cache, no-store"
		this.defaultHeaders["If-Modified-Since"] := "Sat, 1 Jan 2000 00:00:00 GMT"

		return this
	}

	onReadyStateChange()
	{
		if (this.getReadyState() != 4)
		{
			return
		}

		this.isSend := 1
		Func(this.callback).Bind(this).call()
	}

	getReadyState()
	{
		return this.Request.readyState
	}

	send()
	{
		for headerName, headerValue in this.applyHeaedrs
		{
			this.Request.SetRequestHeader(headerName, headerValue)
		}

		this.Request.Send(this.body)

		if (!this.callback)
		{
			this.isSend := 1
		}

		return this
	}

	setHeader(headerName, headerValue)
	{
		if (headerName && headerValue)
		{
			this.applyHeaedrs[headerName] := headerValue
		}

		return this
	}

	getHeader(headerName)
	{
		return this.applyHeaedrs[headerName]
	}

	getApplyHeaders()
	{
		return this.applyHeaedrs
	}

	getDefaultHeader(headerName)
	{
		return this.defaultHeaders[headerName]
	}

	getAllDefaultHeaders()
	{
		return this.defaultHeaders
	}

	setDefaultHeader(headerName, headerValue)
	{
		this.defaultHeaders[headerName] := headerValue

		return this
	}

	applyDefaultHeaders(headers := "")
	{
		if (!headers)
		{
			for headerName, headerValue in this.defaultHeaders
			{
				this.setHeader(headerName, headerValue)
			}
		}
		else
		{
			for k, headerName in headers
			{
				defaultHeaderValue := this.defaultHeaders[headerName]

				if (defaultHeaderValue)
				{
					this.setHeader(headerName, defaultHeader)
				}
			}
		}
		return this
	}

	json()
	{
		return JSON.Parse(this.text())
	}

	text()
	{
		return (this.isSend ? this.Request.ResponseText : "")
	}

	status()
	{
		return this.Request.Status
	}

	statusText()
	{
		return this.Request.StatusText
	}

	GetAllResponseHeaders()
	{
		headers := {}
		if (!this.isSend)
		{
			return headers
		}

		for i, v in strSplit(this.Request.GetAllResponseHeaders(), "`r`n")
		{
			if (!v)
			{
				continue
			}

			if (RegExMatch(v, "UO)^(?<name>.*):\s+(?<params>.*)$", header))
			{
				headerParams := StrReplace(header.params, """", "\""")
				headerParams := StrSplit(headerParams, "; ")

				paramsParsed := {}
				isValues := false
				for i, param in headerParams
				{
					params := StrSplit(param, "=")
					if (params[2])
					{
						isValues := true
					}

					paramsParsed[params[1]] := (params[2] ? params[2] : true)
				}

				headers[header.name] := (isValues ? paramsParsed : headerParams[1])
			}
		}

		return headers
	}
}

Я использую его так:


init := {}
init.method := "GET"
init.headers := {"Content-Type": "application/json"}
init.body := {"test": 1}

response := new Fetch("http://localhost:3000/ahk", init, "ready")
response.setHeader("User-Agent", " ")
response.send()
return

ready(res)
{
	MsgBox, % res.text()
}

UPD: Если указать любой текст в User-Agent, то он применится. Но почему прокатывает пробел для "WinHttp.WinHttpRequest.5.1"?
UPD:
Для "Msxml2.XMLHTTP" не применяется "Referer".
Запрос с "WinHttp.WinHttpRequest.5.1":

init := {}
init.method := "POST"
init.body := {"test": 1}

response := new Fetch("http://localhost:3000/ahk", init)
response.applyDefaultHeaders().setHeader("User-Agent", "AutoHotKey").setHeader("Content-Type", "application/json")

response.send()
return
::1 - - [07/May/2021:01:57:04 +0000] "GET /ahk HTTP/1.1" 200 326 "http://localhost:3000/ahk" "AutoHotKey"

Запрос с "Msxml2.XMLHTTP":

init := {}
init.method := "POST"
init.body := {"test": 1}

response := new Fetch("http://localhost:3000/ahk", init, "ready")
response.applyDefaultHeaders().setHeader("User-Agent", "AutoHotKey").setHeader("Content-Type", "application/json")

response.send()
return

ready(res)
{
	MsgBox, % res.text()
}
::1 - - [07/May/2021:01:53:57 +0000] "GET /ahk HTTP/1.1" 200 355 "-" "AutoHotKey"
Win10: LTSC (21H2); AHK: ANSI (v1.1.36.02)

2 (изменено: Phoenixxx_Czar, 2021-05-07 05:58:15)

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Delete post.

Win10: LTSC (21H2); AHK: ANSI (v1.1.36.02)

3

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Я так и не понял, в чём вопрос.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

4

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Сначала был вопрос в том, что не применяется User-Agent, если указать значение - пробел. Для одного метода работало, для второго нет.
Потом для второго метода не работал хеадер referer.

Win10: LTSC (21H2); AHK: ANSI (v1.1.36.02)

5

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Phoenixxx_Czar пишет:

не применяется User-Agent, если указать значение - пробел

А должен? Там вообще-то формат определённый есть.

Phoenixxx_Czar пишет:

Потом для второго метода не работал хеадер referer.

Попробуйте без класса, может там где-то косяк.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

6

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Phoenixxx_Czar пишет:

Для одного метода работало, для второго нет.

Это не методы, а объекты.
Referer для Msxml2.XMLHTTP указать невозможно.

7 (изменено: Phoenixxx_Czar, 2021-05-07 16:56:37)

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Malcev пишет:

Referer для Msxml2.XMLHTTP указать невозможно.

Спасибо за информацию, а где можно по этому поводу информацию найти? И если не сложно, можете назвать еще как-то ограничения с данным объектом?

teadrinker пишет:

А должен? Там вообще-то формат определённый есть.

Да вот и не знаю должен или нет, просто почему-то для одного объекта если указать объект - сервер просто ставит прочерк, а если для второго объекта, то пишется стандартный юзер агент от этого объекта.

Win10: LTSC (21H2); AHK: ANSI (v1.1.36.02)

8

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

А какой смысл указывать пробел?

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

9

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Не знаю.. Просто скрыть юзер агента..

Win10: LTSC (21H2); AHK: ANSI (v1.1.36.02)

10

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Phoenixxx_Czar пишет:

можете назвать еще как-то ограничения с данным объектом?

https://www.autohotkey.com/boards/viewtopic.php?t=9554

11

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Phoenixxx_Czar пишет:

возникла с "мультипоточным" запросом.

lexikos пишет:

Unlike WinHttpRequest, it can notify us when the request is complete.

На самом деле, WinHttpRequest тоже может.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

12

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Да. infogulch даже враппер написал.
Но надо тестировать - я этим не занимался.
https://github.com/infogulch/AsyncHttp/ … ncHttp.ahk
https://github.com/infogulch/WinHttpReq … equest.ahk

13

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Для 32 бит.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

14

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Как-то так:

#Persistent
url := "https://www.autohotkey.com/assets/images/ahk-logo-no-text241x78-180.png"

Whr := ComObjCreate("WinHttp.WinHttpRequest.5.1")
Events := new IWinHttpRequestEvents(Whr, Func("MyFunc"))
Whr.Open("GET", url, true)
Whr.Send()

MyFunc(info, data) {
   if (info = "error")
      MsgBox,, Error, % "Number: " . data.number . "`nDescription: " . data.desc
   else if (info + 0) {
      File := FileOpen("ahk.png", "w")
      File.RawWrite(data + 0, info)
      File.Close()
      MsgBox, 64,, Data received, .5
   }
}

class IWinHttpRequestEvents
{ ; https://docs.microsoft.com/en-us/windows/win32/winhttp/iwinhttprequestevents-interface
   __New(Whr, UserFunc) {
      this.UserFunc := UserFunc
      this._CreateInterface()
      this._ConnectInterface(Whr)
   }
      
   Status[] {
      get {
         Return this.Info.status
      }
   }
   
   _CreateInterface() {
      static Methods := [ {name: "QueryInterface"         , paramCount: 3}
                        , {name: "AddRef"                 , paramCount: 1}
                        , {name: "Release"                , paramCount: 1}
                        , {name: "OnResponseStart"        , paramCount: 3}
                        , {name: "OnResponseDataAvailable", paramCount: 2}
                        , {name: "OnResponseFinished"     , paramCount: 1}
                        , {name: "OnError"                , paramCount: 3} ]
                        
      this.SetCapacity("vtable", len := A_PtrSize*(Methods.Count() + 1))
      pVtable := this.GetAddress("vtable")
      this.SetCapacity("IUnknown", A_PtrSize)
      NumPut(pVtable, this.GetAddress("IUnknown"))
      
      this.Info := {refOffset: A_PtrSize * Methods.Count(), UserFunc: this.UserFunc}
      this.EventInst := new this.Events(this.Info)
      this.Callbacks := []
      for k, v in Methods {
         Callback := new BoundFuncCallback( ObjBindMethod(this.EventInst, v.name), v.paramCount, "Fast" )
         NumPut(Callback.addr, pVtable + A_PtrSize*(k - 1))
         this.Callbacks.Push(Callback)
      }
      NumPut(0, this.Info.refOffset)
   }
   
   _ConnectInterface(Whr) {
   ; IConnectionPointContainer, IConnectionPoint — OCIdl.h
   ; IWinHttpRequestEvents — httprequest.idl
      static IID_IConnectionPointContainer := "{B196B284-BAB4-101A-B69C-00AA00341D07}"
           , IID_IWinHttpRequestEvents     := "{F97F4E15-B787-4212-80D1-D380CBBF982E}"
           
      pICPC := pIConnectionPointContainer := ComObjQuery(Whr, IID_IConnectionPointContainer)
      riid := CLSIDFromString(IID_IWinHttpRequestEvents, _)
      
      ; IConnectionPointContainer::FindConnectionPoint
      DllCall(NumGet(NumGet(pICPC + 0) + A_PtrSize*4), "Ptr", pICPC, "Ptr", riid, "PtrP", pIConnectionPoint)
      ObjRelease(pICPC), pICP := pIConnectionPoint
      
      ; IConnectionPoint::Advise
      DllCall(NumGet(NumGet(pICP + 0) + A_PtrSize*5), "Ptr", pICP, "Ptr", this.GetAddress("IUnknown"), "UIntP", cookie)
      this.pICP := pICP, this.cookie := cookie
   }
   
   __Delete() {
      ; IConnectionPoint::Unadvise
      DllCall(NumGet(NumGet(this.pICP + 0) + A_PtrSize*6), "Ptr", this.pICP, "UInt", this.cookie)
      ObjRelease(this.pICP)
      this.Delete("Callbacks")
      this.SetCapacity("vtable", 0), this.Delete("vtable")
      this.Delete("EventInst")
   }
   
   class Events {
      __New(Info) {
         this.Info := Info
      }
      
      QueryInterface(pIWinHttpRequestEvents, riid, ppvObject) {
         static IID_IUnknown              := "{00000000-0000-0000-C000-000000000046}"
              , IID_IWinHttpRequestEvents := "{F97F4E15-B787-4212-80D1-D380CBBF982E}"
              , E_NOINTERFACE := 0x80004002, S_OK := 0, _, __
              , p1 := CLSIDFromString(IID_IUnknown             ,  _)
              , p2 := CLSIDFromString(IID_IWinHttpRequestEvents, __)
              
         if !( DllCall("Ole32\IsEqualGUID", "Ptr", riid, "Ptr", p1)
            || DllCall("Ole32\IsEqualGUID", "Ptr", riid, "Ptr", p2) )
         { ; if riid doesn't match IID_IUnknown nor IID_IWinHttpRequestEvents
            NumPut(0, ppvObject + 0)
            Return E_NOINTERFACE
         }
         else {
            NumPut(pIWinHttpRequestEvents, ppvObject + 0)
            DllCall(NumGet(NumGet(ppvObject + 0) + A_PtrSize), "Ptr", ppvObject)
            Return S_OK
         }
      }
      
      AddRef(pIWinHttpRequestEvents) {
         refOffset := pIWinHttpRequestEvents + this.Info.refOffset
         NumPut(refCount := NumGet(refOffset + 0, "UInt") + 1, refOffset, "UInt")
         Return refCount
      }
      
      Release(pIWinHttpRequestEvents) {
         refOffset := pIWinHttpRequestEvents + this.Info.refOffset
         NumPut(refCount := NumGet(refOffset + 0, "UInt") - 1, refOffset, "UInt")
         Return refCount
      }
      
      OnResponseStart(pIWinHttpRequestEvents, status, pType) {
         ; type := StrGet(pType)
         this.Info.status := status
         this.Info.start := true
      }
      
      OnResponseDataAvailable(pIWinHttpRequestEvents, ppSafeArray) {
         Critical
         Info := this.Info
         pSafeArray := NumGet(ppSafeArray + 0)
         pData := NumGet(pSafeArray + 8 + A_PtrSize)
         length := NumGet(pSafeArray + 8 + A_PtrSize*2, "UInt")
         if Info.start {
            Info.start := false
            this.SetCapacity("Data", 0)
            this.SetCapacity("Data", Info.length := length)
            DllCall("RtlMoveMemory", "Ptr", this.GetAddress("Data"), "Ptr", pData, "Ptr", length)
         }
         else {
            this.SetCapacity("Data", Info.length + length)
            DllCall("RtlMoveMemory", "Ptr", this.GetAddress("Data") + Info.length, "Ptr", pData, "Ptr", length)
            Info.length += length
         }
      }
      
      OnResponseFinished(pIWinHttpRequestEvents) {
         this.Info.UserFunc.Call(this.Info.length, this.GetAddress("Data"))
         this.SetCapacity("Data", 0)
      }
      
      OnError(pIWinHttpRequestEvents, errorNumber, pErrorDescription) {
         this.SetCapacity("Data", 0)
         this.Info.UserFunc.Call("error", { number: errorNumber, desc: StrGet(pErrorDescription) })
      }
   }
}

class BoundFuncCallback
{
   __New(BoundFuncObj, paramCount, options := "") {
      this.pInfo := Object( {BoundObj: BoundFuncObj, paramCount: paramCount} )
      this.addr := RegisterCallback(this.__Class . "._Callback", options, paramCount, this.pInfo)
   }
   __Delete() {
      ObjRelease(this.pInfo)
      DllCall("GlobalFree", "Ptr", this.addr, "Ptr")
   }
   _Callback(Params*) {
      Info := Object(A_EventInfo), Args := []
      Loop % Info.paramCount
         Args.Push( NumGet(Params + A_PtrSize*(A_Index - 2)) )
      Return Info.BoundObj.Call(Args*)
   }
}

CLSIDFromString(IID, ByRef CLSID) {
   VarSetCapacity(CLSID, 16, 0)
   if res := DllCall("ole32\CLSIDFromString", "WStr", IID, "Ptr", &CLSID, "UInt")
      throw Exception("CLSIDFromString failed. Error: " . Format("{:#x}", res))
   Return &CLSID
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

15

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

teadrinker пишет:

На самом деле, WinHttpRequest тоже может.

The OnResponseDataAvailable event occurs when data is available from the response.
Что именно это значит? Вызовется ли оно, когда мы допустим пытаемся скачать файл и идет процесс скачки? Или же оно вызывается когда весь файл станет доступным?

Win10: LTSC (21H2); AHK: ANSI (v1.1.36.02)

16

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Перед процессом скачивания и во время его, когда объект получает информацию о том, что есть (или ещё есть) данные для загрузки.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

17

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

То есть по сути можно сделать отображения процесса скачивания файла?
И как тогда получить сколько из скольких скачано?

Win10: LTSC (21H2); AHK: ANSI (v1.1.36.02)

18

Re: AHK: Не применяется SetRequestHeader в Msxml2.XMLHTTP

Когда OnResponseDataAvailable() вызывается в первый раз (блок if Info.start), length — это полный размер данных, потом оставшийся.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder