request-upload.h2.any.js (6608B)
1 // META: global=window,worker 2 // META: script=../resources/utils.js 3 // META: script=/common/utils.js 4 // META: script=/common/get-host-info.sub.js 5 6 const duplex = "half"; 7 8 async function assertUpload(url, method, createBody, expectedBody) { 9 const requestInit = {method}; 10 const body = createBody(); 11 if (body) { 12 requestInit["body"] = body; 13 requestInit.duplex = "half"; 14 } 15 const resp = await fetch(url, requestInit); 16 const text = await resp.text(); 17 assert_equals(text, expectedBody); 18 } 19 20 function testUpload(desc, url, method, createBody, expectedBody) { 21 promise_test(async () => { 22 await assertUpload(url, method, createBody, expectedBody); 23 }, desc); 24 } 25 26 function createStream(chunks) { 27 return new ReadableStream({ 28 start: (controller) => { 29 for (const chunk of chunks) { 30 controller.enqueue(chunk); 31 } 32 controller.close(); 33 } 34 }); 35 } 36 37 const url = RESOURCES_DIR + "echo-content.h2.py" 38 39 testUpload("Fetch with POST with empty ReadableStream", url, 40 "POST", 41 () => { 42 return new ReadableStream({start: controller => { 43 controller.close(); 44 }}) 45 }, 46 ""); 47 48 testUpload("Fetch with POST with ReadableStream", url, 49 "POST", 50 () => { 51 return new ReadableStream({start: controller => { 52 const encoder = new TextEncoder(); 53 controller.enqueue(encoder.encode("Test")); 54 controller.close(); 55 }}) 56 }, 57 "Test"); 58 59 promise_test(async (test) => { 60 const body = new ReadableStream({start: controller => { 61 const encoder = new TextEncoder(); 62 controller.enqueue(encoder.encode("Test")); 63 controller.close(); 64 }}); 65 const resp = await fetch( 66 "/fetch/connection-pool/resources/network-partition-key.py?" 67 + `status=421&uuid=${token()}&partition_id=${self.origin}` 68 + `&dispatch=check_partition&addcounter=true`, 69 {method: "POST", body: body, duplex}); 70 assert_equals(resp.status, 421); 71 const text = await resp.text(); 72 assert_equals(text, "ok. Request was sent 1 times. 1 connections were created."); 73 }, "Fetch with POST with ReadableStream on 421 response should return the response and not retry."); 74 75 promise_test(async (test) => { 76 const request = new Request('', { 77 body: new ReadableStream(), 78 method: 'POST', 79 duplex, 80 }); 81 82 assert_equals(request.headers.get('Content-Type'), null, `Request should not have a content-type set`); 83 84 const response = await fetch('data:a/a;charset=utf-8,test', { 85 method: 'POST', 86 body: new ReadableStream(), 87 duplex, 88 }); 89 90 assert_equals(await response.text(), 'test', `Response has correct body`); 91 }, "Feature detect for POST with ReadableStream"); 92 93 promise_test(async (test) => { 94 const request = new Request('data:a/a;charset=utf-8,test', { 95 body: new ReadableStream(), 96 method: 'POST', 97 duplex, 98 }); 99 100 assert_equals(request.headers.get('Content-Type'), null, `Request should not have a content-type set`); 101 const response = await fetch(request); 102 assert_equals(await response.text(), 'test', `Response has correct body`); 103 }, "Feature detect for POST with ReadableStream, using request object"); 104 105 test(() => { 106 let duplexAccessed = false; 107 108 const request = new Request("", { 109 body: new ReadableStream(), 110 method: "POST", 111 get duplex() { 112 duplexAccessed = true; 113 return "half"; 114 }, 115 }); 116 117 assert_equals( 118 request.headers.get("Content-Type"), 119 null, 120 `Request should not have a content-type set` 121 ); 122 assert_true(duplexAccessed, `duplex dictionary property should be accessed`); 123 }, "Synchronous feature detect"); 124 125 // The asserts the synchronousFeatureDetect isn't broken by a partial implementation. 126 // An earlier feature detect was broken by Safari implementing streaming bodies as part of Request, 127 // but it failed when passed to fetch(). 128 // This tests ensures that UAs must not implement RequestInit.duplex and streaming request bodies without also implementing the fetch() parts. 129 promise_test(async () => { 130 let duplexAccessed = false; 131 132 const request = new Request("", { 133 body: new ReadableStream(), 134 method: "POST", 135 get duplex() { 136 duplexAccessed = true; 137 return "half"; 138 }, 139 }); 140 141 const supported = 142 request.headers.get("Content-Type") === null && duplexAccessed; 143 144 // If the feature detect fails, assume the browser is being truthful (other tests pick up broken cases here) 145 if (!supported) return false; 146 147 await assertUpload( 148 url, 149 "POST", 150 () => 151 new ReadableStream({ 152 start: (controller) => { 153 const encoder = new TextEncoder(); 154 controller.enqueue(encoder.encode("Test")); 155 controller.close(); 156 }, 157 }), 158 "Test" 159 ); 160 }, "Synchronous feature detect fails if feature unsupported"); 161 162 promise_test(async (t) => { 163 const body = createStream(["hello"]); 164 const method = "POST"; 165 await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex })); 166 }, "Streaming upload with body containing a String"); 167 168 promise_test(async (t) => { 169 const body = createStream([null]); 170 const method = "POST"; 171 await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex })); 172 }, "Streaming upload with body containing null"); 173 174 promise_test(async (t) => { 175 const body = createStream([33]); 176 const method = "POST"; 177 await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex })); 178 }, "Streaming upload with body containing a number"); 179 180 promise_test(async (t) => { 181 const url = "/fetch/api/resources/authentication.py?realm=test"; 182 const body = createStream([]); 183 const method = "POST"; 184 await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex })); 185 }, "Streaming upload should fail on a 401 response"); 186 187 promise_test(async (t) => { 188 const abortMessage = 'foo abort'; 189 let streamCancelPromise = new Promise(async res => { 190 var stream = new ReadableStream({ 191 cancel: function(reason) { 192 res(reason); 193 } 194 }); 195 let abortController = new AbortController(); 196 let fetchPromise = promise_rejects_exactly(t, abortMessage, fetch('', { 197 method: 'POST', 198 body: stream, 199 duplex: 'half', 200 signal: abortController.signal 201 })); 202 abortController.abort(abortMessage); 203 await fetchPromise; 204 }); 205 206 let cancelReason = await streamCancelPromise; 207 assert_equals( 208 cancelReason, abortMessage, 'ReadableStream.cancel should be called.'); 209 }, 'ReadbleStream should be closed on signal.abort');