mirror of
				https://github.com/optim-enterprises-bv/Xray-core.git
				synced 2025-10-31 10:38:00 +00:00 
			
		
		
		
	XHTTP server: Fix stream-up "single POST problem", Use united httpServerConn instead of recover()
https://github.com/XTLS/Xray-core/issues/4373#issuecomment-2671795675 https://github.com/XTLS/Xray-core/issues/4406#issuecomment-2668041926
This commit is contained in:
		| @@ -47,21 +47,6 @@ type httpSession struct { | |||||||
| 	isFullyConnected *done.Instance | 	isFullyConnected *done.Instance | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *requestHandler) maybeReapSession(isFullyConnected *done.Instance, sessionId string) { |  | ||||||
| 	shouldReap := done.New() |  | ||||||
| 	go func() { |  | ||||||
| 		time.Sleep(30 * time.Second) |  | ||||||
| 		shouldReap.Close() |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	select { |  | ||||||
| 	case <-isFullyConnected.Wait(): |  | ||||||
| 		return |  | ||||||
| 	case <-shouldReap.Wait(): |  | ||||||
| 		h.sessions.Delete(sessionId) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (h *requestHandler) upsertSession(sessionId string) *httpSession { | func (h *requestHandler) upsertSession(sessionId string) *httpSession { | ||||||
| 	// fast path | 	// fast path | ||||||
| 	currentSessionAny, ok := h.sessions.Load(sessionId) | 	currentSessionAny, ok := h.sessions.Load(sessionId) | ||||||
| @@ -84,7 +69,21 @@ func (h *requestHandler) upsertSession(sessionId string) *httpSession { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	h.sessions.Store(sessionId, s) | 	h.sessions.Store(sessionId, s) | ||||||
| 	go h.maybeReapSession(s.isFullyConnected, sessionId) |  | ||||||
|  | 	shouldReap := done.New() | ||||||
|  | 	go func() { | ||||||
|  | 		time.Sleep(30 * time.Second) | ||||||
|  | 		shouldReap.Close() | ||||||
|  | 	}() | ||||||
|  | 	go func() { | ||||||
|  | 		select { | ||||||
|  | 		case <-shouldReap.Wait(): | ||||||
|  | 			h.sessions.Delete(sessionId) | ||||||
|  | 			s.uploadQueue.Close() | ||||||
|  | 		case <-s.isFullyConnected.Wait(): | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
| 	return s | 	return s | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -183,12 +182,13 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req | |||||||
| 				writer.WriteHeader(http.StatusBadRequest) | 				writer.WriteHeader(http.StatusBadRequest) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 			uploadDone := done.New() | 			httpSC := &httpServerConn{ | ||||||
|  | 				Instance:       done.New(), | ||||||
|  | 				Reader:         request.Body, | ||||||
|  | 				ResponseWriter: writer, | ||||||
|  | 			} | ||||||
| 			err = currentSession.uploadQueue.Push(Packet{ | 			err = currentSession.uploadQueue.Push(Packet{ | ||||||
| 				Reader: &httpRequestBodyReader{ | 				Reader: httpSC, | ||||||
| 					requestReader: request.Body, |  | ||||||
| 					uploadDone:    uploadDone, |  | ||||||
| 				}, |  | ||||||
| 			}) | 			}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				errors.LogInfoInner(context.Background(), err, "failed to upload (PushReader)") | 				errors.LogInfoInner(context.Background(), err, "failed to upload (PushReader)") | ||||||
| @@ -200,25 +200,21 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req | |||||||
| 				scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs() | 				scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs() | ||||||
| 				if referrer != "" && scStreamUpServerSecs.To > 0 { | 				if referrer != "" && scStreamUpServerSecs.To > 0 { | ||||||
| 					go func() { | 					go func() { | ||||||
| 						defer func() { |  | ||||||
| 							recover() |  | ||||||
| 						}() |  | ||||||
| 						for { | 						for { | ||||||
| 							_, err := writer.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand()))) | 							_, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand()))) | ||||||
| 							if err != nil { | 							if err != nil { | ||||||
| 								break | 								break | ||||||
| 							} | 							} | ||||||
| 							writer.(http.Flusher).Flush() |  | ||||||
| 							time.Sleep(time.Duration(scStreamUpServerSecs.rand()) * time.Second) | 							time.Sleep(time.Duration(scStreamUpServerSecs.rand()) * time.Second) | ||||||
| 						} | 						} | ||||||
| 					}() | 					}() | ||||||
| 				} | 				} | ||||||
| 				select { | 				select { | ||||||
| 				case <-request.Context().Done(): | 				case <-request.Context().Done(): | ||||||
| 				case <-uploadDone.Wait(): | 				case <-httpSC.Wait(): | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			uploadDone.Close() | 			httpSC.Close() | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -262,11 +258,6 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req | |||||||
|  |  | ||||||
| 		writer.WriteHeader(http.StatusOK) | 		writer.WriteHeader(http.StatusOK) | ||||||
| 	} else if request.Method == "GET" || sessionId == "" { // stream-down, stream-one | 	} else if request.Method == "GET" || sessionId == "" { // stream-down, stream-one | ||||||
| 		responseFlusher, ok := writer.(http.Flusher) |  | ||||||
| 		if !ok { |  | ||||||
| 			panic("expected http.ResponseWriter to be an http.Flusher") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if sessionId != "" { | 		if sessionId != "" { | ||||||
| 			// after GET is done, the connection is finished. disable automatic | 			// after GET is done, the connection is finished. disable automatic | ||||||
| 			// session reaping, and handle it in defer | 			// session reaping, and handle it in defer | ||||||
| @@ -287,20 +278,18 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		writer.WriteHeader(http.StatusOK) | 		writer.WriteHeader(http.StatusOK) | ||||||
|  | 		writer.(http.Flusher).Flush() | ||||||
|  |  | ||||||
| 		responseFlusher.Flush() | 		httpSC := &httpServerConn{ | ||||||
|  | 			Instance:       done.New(), | ||||||
| 		downloadDone := done.New() | 			Reader:         request.Body, | ||||||
|  | 			ResponseWriter: writer, | ||||||
|  | 		} | ||||||
| 		conn := splitConn{ | 		conn := splitConn{ | ||||||
| 			writer: &httpResponseBodyWriter{ | 			writer:     httpSC, | ||||||
| 				responseWriter:  writer, | 			reader:     httpSC, | ||||||
| 				downloadDone:    downloadDone, |  | ||||||
| 				responseFlusher: responseFlusher, |  | ||||||
| 			}, |  | ||||||
| 			reader:     request.Body, |  | ||||||
| 			localAddr:  h.localAddr, |  | ||||||
| 			remoteAddr: remoteAddr, | 			remoteAddr: remoteAddr, | ||||||
|  | 			localAddr:  h.localAddr, | ||||||
| 		} | 		} | ||||||
| 		if sessionId != "" { // if not stream-one | 		if sessionId != "" { // if not stream-one | ||||||
| 			conn.reader = currentSession.uploadQueue | 			conn.reader = currentSession.uploadQueue | ||||||
| @@ -311,7 +300,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req | |||||||
| 		// "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned." | 		// "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned." | ||||||
| 		select { | 		select { | ||||||
| 		case <-request.Context().Done(): | 		case <-request.Context().Done(): | ||||||
| 		case <-downloadDone.Wait(): | 		case <-httpSC.Wait(): | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		conn.Close() | 		conn.Close() | ||||||
| @@ -321,45 +310,30 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| type httpRequestBodyReader struct { | type httpServerConn struct { | ||||||
| 	requestReader io.ReadCloser |  | ||||||
| 	uploadDone    *done.Instance |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *httpRequestBodyReader) Read(b []byte) (int, error) { |  | ||||||
| 	return c.requestReader.Read(b) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *httpRequestBodyReader) Close() error { |  | ||||||
| 	defer c.uploadDone.Close() |  | ||||||
| 	return c.requestReader.Close() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type httpResponseBodyWriter struct { |  | ||||||
| 	sync.Mutex | 	sync.Mutex | ||||||
| 	responseWriter  http.ResponseWriter | 	*done.Instance | ||||||
| 	responseFlusher http.Flusher | 	io.Reader // no need to Close request.Body | ||||||
| 	downloadDone    *done.Instance | 	http.ResponseWriter | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *httpResponseBodyWriter) Write(b []byte) (int, error) { | func (c *httpServerConn) Write(b []byte) (int, error) { | ||||||
| 	c.Lock() | 	c.Lock() | ||||||
| 	defer c.Unlock() | 	defer c.Unlock() | ||||||
| 	if c.downloadDone.Done() { | 	if c.Done() { | ||||||
| 		return 0, io.ErrClosedPipe | 		return 0, io.ErrClosedPipe | ||||||
| 	} | 	} | ||||||
| 	n, err := c.responseWriter.Write(b) | 	n, err := c.ResponseWriter.Write(b) | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		c.responseFlusher.Flush() | 		c.ResponseWriter.(http.Flusher).Flush() | ||||||
| 	} | 	} | ||||||
| 	return n, err | 	return n, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *httpResponseBodyWriter) Close() error { | func (c *httpServerConn) Close() error { | ||||||
| 	c.Lock() | 	c.Lock() | ||||||
| 	defer c.Unlock() | 	defer c.Unlock() | ||||||
| 	c.downloadDone.Close() | 	return c.Instance.Close() | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type Listener struct { | type Listener struct { | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ type Packet struct { | |||||||
|  |  | ||||||
| type uploadQueue struct { | type uploadQueue struct { | ||||||
| 	reader          io.ReadCloser | 	reader          io.ReadCloser | ||||||
|  | 	nomore          bool | ||||||
| 	pushedPackets   chan Packet | 	pushedPackets   chan Packet | ||||||
| 	writeCloseMutex sync.Mutex | 	writeCloseMutex sync.Mutex | ||||||
| 	heap            uploadHeap | 	heap            uploadHeap | ||||||
| @@ -42,19 +43,15 @@ func (h *uploadQueue) Push(p Packet) error { | |||||||
| 	h.writeCloseMutex.Lock() | 	h.writeCloseMutex.Lock() | ||||||
| 	defer h.writeCloseMutex.Unlock() | 	defer h.writeCloseMutex.Unlock() | ||||||
|  |  | ||||||
| 	runtime.Gosched() |  | ||||||
| 	if h.reader != nil && p.Reader != nil { |  | ||||||
| 		p.Reader.Close() |  | ||||||
| 		return errors.New("h.reader already exists") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if h.closed { | 	if h.closed { | ||||||
| 		if p.Reader != nil { |  | ||||||
| 			p.Reader.Close() |  | ||||||
| 		} |  | ||||||
| 		return errors.New("packet queue closed") | 		return errors.New("packet queue closed") | ||||||
| 	} | 	} | ||||||
|  | 	if h.nomore { | ||||||
|  | 		return errors.New("h.reader already exists") | ||||||
|  | 	} | ||||||
|  | 	if p.Reader != nil { | ||||||
|  | 		h.nomore = true | ||||||
|  | 	} | ||||||
| 	h.pushedPackets <- p | 	h.pushedPackets <- p | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| @@ -65,9 +62,20 @@ func (h *uploadQueue) Close() error { | |||||||
|  |  | ||||||
| 	if !h.closed { | 	if !h.closed { | ||||||
| 		h.closed = true | 		h.closed = true | ||||||
|  | 		runtime.Gosched() // hope Read() gets the packet | ||||||
|  | 	f: | ||||||
|  | 		for { | ||||||
|  | 			select { | ||||||
|  | 			case p := <-h.pushedPackets: | ||||||
|  | 				if p.Reader != nil { | ||||||
|  | 					h.reader = p.Reader | ||||||
|  | 				} | ||||||
|  | 			default: | ||||||
|  | 				break f | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		close(h.pushedPackets) | 		close(h.pushedPackets) | ||||||
| 	} | 	} | ||||||
| 	runtime.Gosched() |  | ||||||
| 	if h.reader != nil { | 	if h.reader != nil { | ||||||
| 		return h.reader.Close() | 		return h.reader.Close() | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 RPRX
					RPRX