Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix hijacking when fulfilling requests with empty body. #603

Merged
merged 2 commits into from
May 19, 2022

Conversation

oderwat
Copy link
Contributor

@oderwat oderwat commented May 16, 2022

The issue occurred when I was writing some tests for a backend that uses Protobuf messages (with Twirp as the transport layer). An empty body is the optimized version of "all fields" in the response message are empty.

If I hijack such a request and just call 'ctx.MustLoadResponse()` without doing anything else, this request suddenly failed. I also could not create this kind of response when I needed it for testing.

After removing the omitempty, it works fine in all my test cases. I actually don't see any reason why you would want to omit when empty. However, maybe I am forgetting some use cases.

@ysmood
Copy link
Member

ysmood commented May 16, 2022

Your change will be overwritten:

// This file is generated by "./lib/proto/generate"

@ysmood
Copy link
Member

ysmood commented May 16, 2022

Can you add a test case that will fail when we use omitempty?

@oderwat
Copy link
Contributor Author

oderwat commented May 17, 2022

Your change will be overwritten:

Yes. I was too excited to see it work after hours of trying to find the problem with my code. So what can we do about this?

@ysmood
Copy link
Member

ysmood commented May 17, 2022

You can use this patch.go file to patch the description of the body, remove the optional like .Del("optional").

@oderwat
Copy link
Contributor Author

oderwat commented May 17, 2022

Can you add a test case that will fail when we use omitempty?

Probably, but I did not dig into the code and what is really happening. From what my tests looked like at first was that the Twirp frontend code kind of ignores the whole request without an error.

My (crafted) response looks like this.

	ctx.Response.SetBody([]byte{}).SetHeader(
		"Access-Control-Allow-Origin", siteURL,
		"Access-Control-Expose-Headers", "Link",
		"Cache-Control", "public, max-age=60",
		"Content-Length", "0",
		"Content-Type", "application/protobuf",
		"Vary", "Origin",
	)

I did some more testing and when I use this with the unmodified rod, the Twirp code does not return from sending the request. After removing the omitempty it works just as it works when it is not hijacking it at all (I just used go.work and the single modification for testing).

To make it a bit more interesting the Twirp code runs as WebAssembly code in the browser front end. It is a goroutine and I am sure that it does not crash. My guess is that it waits for more data from the server as it expects a body (there is a content-length header which besides the status code also means that there is a body. So it may just wait and hangs and may eventually time out, but at that time the tests are already completed.

I am writing my own little test framework and it was based on ChromeDP at first. I did the same interceptions there and it worked, so there must be something different. I know that I have to base64 convert my body there, but did not check how they do the serialization for the CDP.

So yes, I may be able to figure out how to test for it, but I actually would prefer to just reason about the fact that not having a body in the response (200 OK) somehow is "illegal" anyway. It would need to be 204 (No Content) when I don't send a body, and it would not be allowed to include a "content-length" header. So I think that the request "crafting" can't simply omit the body when the code is not 204 or other codes that do not have one.

That thought again leads me to a possible test when checking the raw TCP response and looking at how many lines are sent after the headers. But that is not possible, I guess because there is not really a TCP port to listen to :)

@ysmood
Copy link
Member

ysmood commented May 17, 2022

The info you provide is overwhelming for me, I don't know Twirp.

After removing the omitempty, it works fine in all my test cases. I actually don't see any reason why you would want to omit when empty. However, maybe I am forgetting some use cases.

It's not us, but chrome devtools protocol wants us to omitempty.

Can you provide steps to reproduce the issue?

@oderwat
Copy link
Contributor Author

oderwat commented May 17, 2022

Sorry, but I had the hope, that giving as much detail as I can ring a bell. For reference Twirp

For steps to reproduce the problem, I need to figure out a minimal example project. I guess I can build one but I need some time for that.

So I checked it again: The chromed implementation also has body,omitempty in the source and when I create an actual empty body there, it works (no changes in the app which is being tested). So something else must be different between both implementations.

One difference between using chromedp and rod I noticed is that I have to use base64 encoding for the protobuf data in chromedp and not in rod. I also see that in the chromedp code they are handling the res.Base64encoded flag in their code. But this does not apply to the simple case of having an empty body as result anyway and it seems to work with normal bodies encoded as protobuf (so far).

The further investigation makes me think that using chromedp implies to use https://chromedevtools.github.io/devtools-protocol/tot/Fetch#method-getResponseBody to get the response body, while rod is using its own http.Client. So I guess there will be no throttling and maybe other things that happen when the request is actually sent by the browser. But this should not be related to the problem I encounter here, as I manufacture the whole response.

I still can't find a reason why the response with an empty body fails. So I try to create an example that reproduces this problem.

@ysmood
Copy link
Member

ysmood commented May 17, 2022

You can print the raw cdp log from both rod and chromedp, and compare the difference. You can check their doc to print the raw cdp.

For rod you can simply use cli flag -rod=cdp or:

func Example_customize_cdp_log() {
ws := cdp.MustConnectWS(launcher.New().MustLaunch())
cdp.New().
Logger(utils.Log(func(args ...interface{}) {
switch v := args[0].(type) {
case *cdp.Request:
fmt.Printf("id: %d", v.ID)
}
})).
Start(ws)
}

@oderwat
Copy link
Contributor Author

oderwat commented May 17, 2022

You can print the raw cdp log from both rod and chromedp, and compare the difference.

Good idea! I am currently trying to write a minimal example. But may need to stop and do something else. I come back to it soon.

@ysmood
Copy link
Member

ysmood commented May 17, 2022

FYI

chromedp.NewContext(allocCtx, chromedp.WithDebugf(log.Printf))

@oderwat
Copy link
Contributor Author

oderwat commented May 17, 2022

Thank you. I already have quite the large testing framework and logging the CDP is part of that (together with console, exceptions, and request logging). Currently, I suspect the problem is somehow related to the fetch implementation of WASM, as it seems to work fine with XMLHttpRequest() in simple javascript.

@oderwat
Copy link
Contributor Author

oderwat commented May 17, 2022

Maybe as a little background: I started to write my own testing framework based on chromedp and made it especially to test PWA that are written using go-app which uses WASM for the frontend. It abstracts many things and makes everything easily traceable with nice logging output (supposed to be structural later).

image

For the implementation, I am partially close to the ideas that are rod using. So I was thinking about replacing chromedp with rod for my framework as I am currently having quite some overhead as I "run" every action right now. I also like the clear code of rod much better.

I use a chain for the tests which traces every call and when an error happens it skips all further steps to the end or to a "catch" phrase (this is WIP). I also have something like "slow motion" as "TimeBetweenActions", "wait for idle requests" as "RequestsIdle" and "hijacking" as "AddInterception"

	var settings sp.Settings
	sr.Start().
		CallerLog(false).
		SetTimeout(30*time.Second).
		//TimeBetweenActions(2*time.Second).
		TimeBetweenActions(20*time.Millisecond).
		NavigateWait(url, "app-wasm-loader", sp.ByID, sp.NodeNotPresent).
		Log("Fake Login as User").
		Click(`a[href="/als-benutzer-anmelden"]`, sp.ByQuery, sp.NodeVisible, sp.NodeReady).
		WaitVisible("#pagecontent div.prose").
		Text("#pagecontent", &text).
		Dispatch(func(c *sp.Snoop) error {
			c.Logf("Text is %d bytes", len(text))
			return nil
		}).
		MustContainText("#pagecontent", "Lorem ipsum").
		Dispatch(func(c *sp.Snoop) error {
			return login(c, "test", "Me")
		}).
		Catch().
		// Execute this as fast as possible (not sure if my implementation is good enough)
		// The input in the textarea is denounced by 200 ms so we need to wait idle for quite some time
		Log("# Test Markdown").
		StoreSettings(&settings).
		TimeBetweenActions(0*time.Second).
		ConsoleLog(true).
		RequestLog(true).
		AddInterception("**/twirp/v1.API/ConvertToMarkdown", interceptMarkdownPassthrough).
		Click(`div.d-sidebar a[data-pcode="md2html"]`).
		Query("#pagecontent textarea", sp.NodeVisible).
		RequestsIdle(300*time.Millisecond).
		SetValue("#pagecontent textarea", "").
		RequestsIdle(300*time.Millisecond).
		InnerHTML("#pagecontent div.prose", &text, sp.NodeReady).
		Dispatch(func(c *sp.Snoop) error {
			c.InnerHTML("#pagecontent div.prose", &text).
				Logf("InnerHTML is %q", text)
			return nil
		}).
		// Replaces the existing one
		AddInterception("**/twirp/v1.API/ConvertToMarkdown", interceptMarkdownReplace).
		SendKeys("#pagecontent textarea", "# Überschrift\nTextinhalt\n", sp.ByQuery).
		// Need to wait for backend update (something that should actually guarantee it)
		RequestsIdle(300*time.Millisecond).
		InnerHTML("#pagecontent div.prose", &text, sp.NodeReady).
		Dispatch(func(c *sp.Snoop) error {
			c.InnerHTML("#pagecontent div.prose", &text).
				Logf("InnerHTML is %q", text)
			return nil
		}).
		//
		DelInterception("**/twirp/v1.API/ConvertToMarkdown").
		SendKeys("#pagecontent textarea", "> Interception removed", sp.ByQuery).
		RequestsIdle(300*time.Millisecond).
		InnerHTML("#pagecontent div.prose", &text, sp.NodeReady).
		Dispatch(func(c *sp.Snoop) error {
			c.InnerHTML("#pagecontent div.prose", &text).
				Logf("InnerHTML is %q", text)
			return nil
		}).
		RestoreSettings(&settings).
		Click(`//div[contains(@class,'d-sidebar')]//div/p[text()="Examples"]/..`).
		WaitVisible(`div.d-sidebar a[data-pcode="form"]`).
		Click(`div.d-sidebar a[data-pcode="mdmissing"]`).
		RequestsIdle(25*time.Millisecond).
		MustContainText("#pagecontent", "Error: Data could not be loaded").
		CatchEnd()

This is how it all started.

@oderwat oderwat marked this pull request as draft May 17, 2022 15:01
The issue occurred when I was writing some tests for a backend that uses Protobuf messages (with Twirp as the transport layer). An empty body is the optimized version of "all fields" in the response message are empty.

If I hijack such a request and just call 'ctx.MustLoadResponse()` without doing anything else, this request suddenly failed. I also could not create this kind of response when I needed it for testing.

After removing the omitempty, it works fine in all my test cases. I actually don't see any reason why you would want to omit when empty. However, maybe I am forgetting some use cases.
@oderwat
Copy link
Contributor Author

oderwat commented May 17, 2022

I wrote a (fairly) simple tester as an example of how to reproduce the problem.

rodgoapp

It does some tests and shows what actually works well and where the problem arises.

P.S.: There is no Twirp needed to get the problem to show up. It happens with the standard http.Client (running as WebAssembly code). I want to stress, that a similar example works with ChromeDP, but I still do not know why.

@maxence-charriere you probably like to have a look at the testing demonstration (I know you are very busy)

@ysmood
Copy link
Member

ysmood commented May 18, 2022

I have tried your demo code. The second /empty post request from the frontend doesn't have content-length header, I have no idea why this is happening.

I think it's safe to remove the omitempty

@oderwat
Copy link
Contributor Author

oderwat commented May 18, 2022

I think it's safe to remove the omitempty

The problem with that is, that it will be not possible to generate valid requests that have no body. They quite very seldom though. I may try to find out why it works with chromedp again first.

@ysmood
Copy link
Member

ysmood commented May 18, 2022

The problem with that is, that it will be not possible to generate valid requests that have no body.

[]byte{} can present requests that have no body, it will be encoded to base64 "", the content-length is still zero.

The difference between them is {} and {"body": ""}. I also tested {"body": null}, which will even cause an error.

@oderwat
Copy link
Contributor Author

oderwat commented May 18, 2022

I create the proto patch and tried it with and without SetBody() and I can make it have an empty and no body. So I guess this actually really solves the problem. I still have a little bad feeling left, because I do not understand how chromedp does it without patching it (at least that is how it looked to me).

I could also add test cases for checking if the empty body works and for the no body at all case. Both tests depend on the browser locking up (and there is not even WASM needed) which is kind of the behavior it has without removing the omitempty for the first test. Do you have a better idea for testing it?

While I am at it, what do you think about my observation that Rod does a raw http.DefaultClient request on "the side" while there is actually a proto call for getting the body through the browser (and its network conditioner, proxy and maybe even traps or whatever)?

Added a patch for proto / fetch and tests for empty and no body
responses
@oderwat oderwat marked this pull request as ready for review May 18, 2022 14:14
@ysmood
Copy link
Member

ysmood commented May 18, 2022

because I do not understand how chromedp does it without patching it (at least that is how it looked to me).

Can you put the raw cdp output of chromedp here to invest?

While I am at it, what do you think about my observation that Rod does a raw http.DefaultClient request on "the side" while there is actually a proto call for getting the body through the browser (and its network conditioner, proxy and maybe even traps or whatever)?

If we use FetchFulfillRequest, the browser won't actually send the request to the server, so we don't have to worry about it.

I plan to drop the use of the Fetch domain because it's too buggy, you should also check this issue #395

@oderwat
Copy link
Contributor Author

oderwat commented May 19, 2022

If we use FetchFulfillRequest, the browser won't actually send the request to the server, so we don't have to worry about it.

But I am talking about https://chromedevtools.github.io/devtools-protocol/tot/Fetch#method-getResponseBody which first fetches the original response and then lets me modify it. Rod does this using the raw HTTP request. I use that in my test framework to modify answers instead of building them from scratch.

Can you put the raw CDP output of chromedp here to invest?

Yes, I just cutted it and changed the hostnames a bit. The log even contains our private repository paths otherwise.

It starts with the requestWillBeSent for ConvertToMarkdown

022/05/19 02:39:57.913848 [C] pkg/pages.(*MD2HTML).updateAll.func1: Sending the Request
2022/05/19 02:39:57.914116 <- {"method":"Network.requestWillBeSent","params":{"requestId":"1728.115","loaderId":"23B03188C90FE5828CEAE6F263BC3F62","documentURL":"https://mycoolserver.io:8500/admin/md2html","request":{"url":"https://mycoolserver.io:8500/twirp/v1.API/ConvertToMarkdown","method":"POST","headers":{"sec-ch-ua":"\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Google Chrome\";v=\"101\"","skelly-client-time":"2022-05-19T02:39:57+02:00","sec-ch-ua-mobile":"?0","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36","twirp-version":"v8.1.2","skelly-device-id":"6f9e977b-451e-4b34-b327-a0daac77de8e","accept":"application/protobuf","Referer":"https://mycoolserver.io:8500/admin/md2html","skelly-window-id":"e76e17d4-07a9-4d84-8664-a0acc0be835c","content-type":"application/protobuf","sec-ch-ua-platform":"\"macOS\""},"postData":"\n\u0006mdcode\u0010\u0001","hasPostData":true,"postDataEntries":[{"bytes":"CgZtZGNvZGUQAQ=="}],"mixedContentType":"none","initialPriority":"High","referrerPolicy":"strict-origin-when-cross-origin","isSameSite":true},"timestamp":549443.717334,"wallTime":1652920797.913782,"initiator":{"type":"script","stack":{"callFrames":[{"functionName":"syscall/js.valueCall","scriptId":"8","url":"https://mycoolserver.io:8500/wasm_exec.js","lineNumber":348,"columnNumber":30},{"functionName":"$syscall_js.valueCall","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":1113259},{"functionName":"$syscall_js.Value.Call","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":1102172},{"functionName":"$net_http.__Transport_.RoundTrip","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":5654401},{"functionName":"$net_http.send","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":5220023},{"functionName":"$net_http.__Client_.send","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":5214792},{"functionName":"$net_http.__Client_.do","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":5239809},{"functionName":"$net_http.__Client_.Do","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":5232872},{"functionName":"$git.jetbrains.space_mysupersecret_rpc_api.doProtobufRequest","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":9538867},{"functionName":"$git.jetbrains.space_mysupersecret_rpc_api.__aPIProtobufClient_.callConvertToMarkdown","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":9474494},{"functionName":"$git.jetbrains.space_mysupersecret_rpc_api.__aPIProtobufClient_.callConvertToMarkdown_fm","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":9560828},{"functionName":"$git.jetbrains.space_mysupersecret_rpc_api.__aPIProtobufClient_.ConvertToMarkdown","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":9472263},{"functionName":"$git.jetbrains.space_mysupersecret_pkg_pages.__MD2HTML_.updateAll.func1","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":10193512},{"functionName":"$wasm_pc_f_loop","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":969409},{"functionName":"$wasm_export_resume","scriptId":"10","url":"wasm://wasm/045b57f6","lineNumber":0,"columnNumber":969378},{"functionName":"_resume","scriptId":"8","url":"https://mycoolserver.io:8500/wasm_exec.js","lineNumber":537,"columnNumber":22},{"functionName":"","scriptId":"8","url":"https://mycoolserver.io:8500/wasm_exec.js","lineNumber":548,"columnNumber":7}]}},"redirectHasExtraInfo":false,"type":"Fetch","frameId":"6CB676FB2CCC52D1E1237E62D536FCE5","hasUserGesture":true},"sessionId":"3433A9123B551FFD848E4E59F474C750"}
2022/05/19 02:39:57.914250 <- {"method":"Network.loadingFinished","params":{"requestId":"1728.114","timestamp":549443.715686,"encodedDataLength":264,"shouldReportCorbBlocking":false},"sessionId":"3433A9123B551FFD848E4E59F474C750"}
2022/05/19 02:39:57.914283 [R#1728.115< POST https://mycoolserver.io:8500/twirp/v1.API/ConvertToMarkdown

Then this is the part where I hook into fetch.EventRequestPaused

2022/05/19 02:39:57.914286 <- {"method":"Fetch.requestPaused","params":{"requestId":"interception-job-23.0","request":{"url":"https://mycoolserver.io:8500/twirp/v1.API/ConvertToMarkdown","method":"POST","headers":{"Origin":"https://mycoolserver.io:8500","Referer":"https://mycoolserver.io:8500/admin/md2html","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36","accept":"application/protobuf","content-type":"application/protobuf","sec-ch-ua":"\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Google Chrome\";v=\"101\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"macOS\"","skelly-client-time":"2022-05-19T02:39:57+02:00","skelly-device-id":"6f9e977b-451e-4b34-b327-a0daac77de8e","skelly-window-id":"e76e17d4-07a9-4d84-8664-a0acc0be835c","twirp-version":"v8.1.2"},"postData":"\n\u0006mdcode\u0010\u0001","hasPostData":true,"postDataEntries":[{"bytes":"CgZtZGNvZGUQAQ=="}],"initialPriority":"High","referrerPolicy":"strict-origin-when-cross-origin"},"frameId":"6CB676FB2CCC52D1E1237E62D536FCE5","resourceType":"XHR","networkId":"1728.115"},"sessionId":"3433A9123B551FFD848E4E59F474C750"}

And use return fetch.FulfillRequest(rid, 200).WithBody("")

2022/05/19 02:39:57.914340 >R#Intercept< https://mycoolserver.io:8500/twirp/v1.API/ConvertToMarkdown
2022/05/19 02:39:57.914357 -> {"id":299,"sessionId":"3433A9123B551FFD848E4E59F474C750","method":"Fetch.fulfillRequest","params":{"requestId":"interception-job-23.0","responseCode":200}}
2022/05/19 02:39:57.914504 <- {"id":299,"result":{},"sessionId":"3433A9123B551FFD848E4E59F474C750"}
2022/05/19 02:39:57.914924 <- {"method":"Network.responseReceived","params":{"requestId":"1728.115","loaderId":"23B03188C90FE5828CEAE6F263BC3F62","timestamp":549443.718167,"type":"Fetch","response":{"url":"https://mycoolserver.io:8500/twirp/v1.API/ConvertToMarkdown","status":200,"statusText":"OK","headers":{},"mimeType":"","connectionReused":false,"connectionId":0,"fromDiskCache":false,"fromServiceWorker":false,"fromPrefetchCache":false,"encodedDataLength":17,"timing":{"requestTime":549443.717808,"proxyStart":-1,"proxyEnd":-1,"dnsStart":-1,"dnsEnd":-1,"connectStart":-1,"connectEnd":-1,"sslStart":-1,"sslEnd":-1,"workerStart":-1,"workerReady":-1,"workerFetchStart":-1,"workerRespondWithSettled":-1,"sendStart":-1,"sendEnd":-1,"pushStart":0,"pushEnd":0,"receiveHeadersEnd":0.252},"responseTime":1.652920797914423e+12,"protocol":"http/1.1","securityState":"unknown"},"hasExtraInfo":false,"frameId":"6CB676FB2CCC52D1E1237E62D536FCE5"},"sessionId":"3433A9123B551FFD848E4E59F474C750"}
2022/05/19 02:39:57.914949 <- {"method":"Network.loadingFinished","params":{"requestId":"1728.115","timestamp":549443.71806,"encodedDataLength":17,"shouldReportCorbBlocking":false},"sessionId":"3433A9123B551FFD848E4E59F474C750"}
2022/05/19 02:39:57.914967 >R#1728.115] (200) https://mycoolserver.io:8500/twirp/v1.API/ConvertToMarkdown
2022/05/19 02:39:58.305833 >S#13] -> DONE (0.000s)

@ysmood
Copy link
Member

ysmood commented May 19, 2022

But I am talking about https://chromedevtools.github.io/devtools-protocol/tot/Fetch#method-getResponseBody

I see. I think we need to redesign the hijack a little bit to support it, or the users can use proto.FetchGetResponseBody directly like chromedp. We can create an issue for it.

And use return fetch.FulfillRequest(rid, 200).WithBody("")

There's no difference. Can it be that you have used the getResponseBody before the FulfillRequest?

@oderwat
Copy link
Contributor Author

oderwat commented May 19, 2022

There's no difference. Can it be that you have used the getResponseBody before the FulfillRequest?

No, and the request also does not show up in the backend log.

I can show you my (crude) code:

gotException := make(chan bool, 1)
	chromedp.ListenTarget(sr.Context(), func(ev interface{}) {
		switch ev := ev.(type) {
		case *rt.EventConsoleAPICalled:
			//fmt.Printf("* console.%s call:\n", ev.Type)
			if sr.logConsole {
				for _, arg := range ev.Args {
					sr.logf("%s[C] %s%s\n", color.Cyan, strings.Trim(string(arg.Value), `"`), color.Reset)
				}
			}

		case *network.EventRequestWillBeSent:
			sr.openRequests++
			if sr.logRequests {
				sr.logf("%s[R#%s< %s %s%s", color.Blue, ev.RequestID, ev.Request.Method, elipse(ev.Request.URL, 80), color.Reset)
			}

		case *network.EventResponseReceived:
			sr.openRequests--
			sr.lastResponse = time.Now()
			if sr.expectRequests != 0 && sr.lastResponse.After(sr.expectAfter) {
				sr.expectHappened++
			}
			if sr.logRequests {
				sr.logf("%s>R#%s] (%d) %s%s", color.Blue, ev.RequestID, ev.Response.Status, elipse(ev.Response.URL, 80), color.Reset)
			}

		case *fetch.EventRequestPaused:
			go func(ctx context.Context, ev *fetch.EventRequestPaused) {
				// this is where we intercept requests
				var a chromedp.Action
				if ev.ResponseStatusCode != 0 {
					a = fetch.ContinueResponse(ev.RequestID)
				} else {
					for _, ic := range sr.Interceptions {
						match := ic.glob.Match(ev.Request.URL)
						if match {
							sr.logf("%s>R#Intercept< %s%s", color.LightBlue, ev.Request.URL, color.Reset)
							a = ic.Interceptor(sr, ev.RequestID, ev.Request)
						}
					}
					if a == nil {
						a = fetch.ContinueRequest(ev.RequestID)
					}
				}
				if err := chromedp.Run(ctx, a); err != nil {
					log.Println(fmt.Errorf("EventRP Error: %w", err))
				}
			}(sr.ctx, ev)
		}
	})
func interceptMarkdownEmpty(t *sp.Snooper, rid fetch.RequestID, req *network.Request) chromedp.Action {
	h := req.Headers
	if h["accept"] == "application/protobuf" {
		return fetch.FulfillRequest(rid, 200).WithBody("")
		//return fetch.FulfillRequest(rid, 200) //.WithBody(base64.StdEncoding.EncodeToString(respBytes))
	} else {
		panic("Intercepting 'accept' of unknown format")
	}
}

@ysmood
Copy link
Member

ysmood commented May 19, 2022

OK, let's merge it first. I created the issue for getResponseBody: #607

@ysmood ysmood enabled auto-merge (rebase) May 19, 2022 02:18
@ysmood ysmood merged commit 05b55fb into go-rod:master May 19, 2022
@oderwat oderwat deleted the FixEmptyBody branch May 19, 2022 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants