Tomcat源码篇-解析请求体

一、准备解析Http11Processor#prepareRequest

private void prepareRequest() throws IOException {

        contentDelimitation = false;

        if (endpoint.isSSLEnabled()) {
            request.scheme().setString("https");
        }

        MimeHeaders headers = request.getMimeHeaders();

        // Check connection header
        // 判断是否支持keepalive
        MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION);
        if (connectionValueMB != null && !connectionValueMB.isNull()) {
            Set<String> tokens = new HashSet<>();
            TokenList.parseTokenList(headers.values(Constants.CONNECTION), tokens);
            if (tokens.contains(Constants.CLOSE)) {
                keepAlive = false;
            } else if (tokens.contains(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN)) {
                keepAlive = true;
            }
        }

        if (http11) {
            MessageBytes expectMB = headers.getValue("expect");
            if (expectMB != null && !expectMB.isNull()) {
                if (expectMB.toString().trim().equalsIgnoreCase("100-continue")) {
                    inputBuffer.setSwallowInput(false);
                    request.setExpectation(true);
                } else {
                    response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
                    setErrorState(ErrorState.CLOSE_CLEAN, null);
                }
            }
        }

        // Check user-agent header
        if (restrictedUserAgents != null && (http11 || keepAlive)) {
            MessageBytes userAgentValueMB = headers.getValue("user-agent");
            // Check in the restricted list, and adjust the http11
            // and keepAlive flags accordingly
            if(userAgentValueMB != null && !userAgentValueMB.isNull()) {
                String userAgentValue = userAgentValueMB.toString();
                if (restrictedUserAgents != null &&
                        restrictedUserAgents.matcher(userAgentValue).matches()) {
                    http11 = false;
                    keepAlive = false;
                }
            }
        }


        // Check host header
        MessageBytes hostValueMB = null;
        try {
            hostValueMB = headers.getUniqueValue("host");
        } catch (IllegalArgumentException iae) {
            // Multiple Host headers are not permitted
            badRequest("http11processor.request.multipleHosts");
        }
        if (http11 && hostValueMB == null) {
            badRequest("http11processor.request.noHostHeader");
        }

        // Check for an absolute-URI less the query string which has already
        // been removed during the parsing of the request line
        ByteChunk uriBC = request.requestURI().getByteChunk();
        byte[] uriB = uriBC.getBytes();
        if (uriBC.startsWithIgnoreCase("http", 0)) {
            int pos = 4;
            // Check for https
            if (uriBC.startsWithIgnoreCase("s", pos)) {
                pos++;
            }
            // Next 3 characters must be "://"
            if (uriBC.startsWith("://", pos)) {
                pos += 3;
                int uriBCStart = uriBC.getStart();

                // '/' does not appear in the authority so use the first
                // instance to split the authority and the path segments
                int slashPos = uriBC.indexOf('/', pos);
                // '@' in the authority delimits the userinfo
                int atPos = uriBC.indexOf('@', pos);
                if (slashPos > -1 && atPos > slashPos) {
                    // First '@' is in the path segments so no userinfo
                    atPos = -1;
                }

                if (slashPos == -1) {
                    slashPos = uriBC.getLength();
                    // Set URI as "/". Use 6 as it will always be a '/'.
                    // 01234567
                    // http://
                    // https://
                    request.requestURI().setBytes(uriB, uriBCStart + 6, 1);
                } else {
                    request.requestURI().setBytes(uriB, uriBCStart + slashPos, uriBC.getLength() - slashPos);
                }

                // Skip any user info
                if (atPos != -1) {
                    // Validate the userinfo
                    for (; pos < atPos; pos++) {
                        byte c = uriB[uriBCStart + pos];
                        if (!HttpParser.isUserInfo(c)) {
                            // Strictly there needs to be a check for valid %nn
                            // encoding here but skip it since it will never be
                            // decoded because the userinfo is ignored
                            badRequest("http11processor.request.invalidUserInfo");
                            break;
                        }
                    }
                    // Skip the '@'
                    pos = atPos + 1;
                }

                if (http11) {
                    // Missing host header is illegal but handled above
                    if (hostValueMB != null) {
                        // Any host in the request line must be consistent with
                        // the Host header
                        if (!hostValueMB.getByteChunk().equals(
                                uriB, uriBCStart + pos, slashPos - pos)) {
                            if (protocol.getAllowHostHeaderMismatch()) {
                                // The requirements of RFC 2616 are being
                                // applied. If the host header and the request
                                // line do not agree, the request line takes
                                // precedence
                                hostValueMB = headers.setValue("host");
                                hostValueMB.setBytes(uriB, uriBCStart + pos, slashPos - pos);
                            } else {
                                // The requirements of RFC 7230 are being
                                // applied. If the host header and the request
                                // line do not agree, trigger a 400 response.
                                badRequest("http11processor.request.inconsistentHosts");
                            }
                        }
                    }
                } else {
                    // Not HTTP/1.1 - no Host header so generate one since
                    // Tomcat internals assume it is set
                    try {
                        hostValueMB = headers.setValue("host");
                        hostValueMB.setBytes(uriB, uriBCStart + pos, slashPos - pos);
                    } catch (IllegalStateException e) {
                        // Edge case
                        // If the request has too many headers it won't be
                        // possible to create the host header. Ignore this as
                        // processing won't reach the point where the Tomcat
                        // internals expect there to be a host header.
                    }
                }
            } else {
                badRequest("http11processor.request.invalidScheme");
            }
        }

        // Validate the characters in the URI. %nn decoding will be checked at
        // the point of decoding.
        for (int i = uriBC.getStart(); i < uriBC.getEnd(); i++) {
            if (!httpParser.isAbsolutePathRelaxed(uriB[i])) {
                badRequest("http11processor.request.invalidUri");
                break;
            }
        }

        // Input filter setup
        InputFilter[] inputFilters = inputBuffer.getFilters();

        // Parse transfer-encoding header
        if (http11) {
            MessageBytes transferEncodingValueMB = headers.getValue("transfer-encoding");
            if (transferEncodingValueMB != null) {
                List<String> encodingNames = new ArrayList<>();
                if (TokenList.parseTokenList(headers.values("transfer-encoding"), encodingNames)) {
                    for (String encodingName : encodingNames) {
                        // "identity" codings are ignored
                        // 根据encoding选择对应的inputfilter
                        // GO
                        addInputFilter(inputFilters, encodingName);
                    }
                } else {
                    // Invalid transfer encoding
                    badRequest("http11processor.request.invalidTransferEncoding");
                }
            }
        }

        // Parse content-length header
        long contentLength = -1;
        try {
            contentLength = request.getContentLengthLong();
        } catch (NumberFormatException e) {
            badRequest("http11processor.request.nonNumericContentLength");
        } catch (IllegalArgumentException e) {
            badRequest("http11processor.request.multipleContentLength");
        }
        if (contentLength >= 0) {
            // contentDelimitation为true,及代表出现transfer-encoding header,则忽略content-length
            if (contentDelimitation) {
                // contentDelimitation being true at this point indicates that
                // chunked encoding is being used but chunked encoding should
                // not be used with a content length. RFC 2616, section 4.4,
                // bullet 3 states Content-Length must be ignored in this case -
                // so remove it.
                headers.removeHeader("content-length");
                request.setContentLength(-1);
            } else {
                // 没有出现chunked,则是普通的body,指定读取request body的filter为identityFilter解析
                inputBuffer.addActiveFilter(inputFilters[Constants.IDENTITY_FILTER]);
                contentDelimitation = true;
            }
        }

        // Validate host name and extract port if present
        parseHost(hostValueMB);

        if (!contentDelimitation) {
            // If there's no content length
            // (broken HTTP/1.0 or HTTP/1.1), assume
            // the client is not broken and didn't send a body
            inputBuffer.addActiveFilter(inputFilters[Constants.VOID_FILTER]);
            contentDelimitation = true;
        }

        if (!getErrorState().isIoAllowed()) {
            getAdapter().log(request, response, 0);
        }
    }
  • 判断是否支持keepalive
  • 判断是否支持expect,当post的数据大于1024字节的时候,不会直接就发起POST请求,而是分为2步
    • 发送一个请求, 包含一个Expect:100-continue, 询问Server使用愿意接受数据
    • 接收到Server返回的100-continue应答以后, 才把数据POST给Server
  • 检查user-agent
  • 检查host
  • 检查URI
  • 检查transfer-encoding
    • 如果是"chunked",添加ChunkedInputFilter,并将contentDelimitation置为true
    • 根据其它名称添加相应的InputFilter
    • 判断contentDelimitation是否为true:若为true,设置content-length为-1;若不为true,添加IdentityInputFilter
    • 如果没有content-length,则添加VoidInputFilter

二、解析请求体

1、org.apache.catalina.connector.Request#getParameter
    @Override
    public String getParameter(String name) {

        // parametersParsed默认是false,解析完后设置为true,防止重复读
        if (!parametersParsed) {
            // GO
            parseParameters();
        }

        return coyoteRequest.getParameters().getParameter(name);

    }
  • 请求体是在调用getParameter的时候开始解析的
  • 解析前先判断是否解析,避免重复解析
  • 解析后获取参数值

2、org.apache.catalina.connector.Request#parseParameters

protected void parseParameters() {

        parametersParsed = true;

        Parameters parameters = coyoteRequest.getParameters();
        boolean success = false;
        try {
            // Set this every time in case limit has been changed via JMX
            parameters.setLimit(getConnector().getMaxParameterCount());

            // getCharacterEncoding() may have been overridden to search for
            // hidden form field containing request encoding
            Charset charset = getCharset();

            boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
            parameters.setCharset(charset);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringCharset(charset);
            }
            // Note: If !useBodyEncodingForURI, the query string encoding is
            //       that set towards the start of CoyoyeAdapter.service()

            // 解析url后面的参数
            parameters.handleQueryParameters();

            // usingInputStream为true,或者usingReader为true则直接返回
            if (usingInputStream || usingReader) {
                success = true;
                return;
            }

            String contentType = getContentType();
            if (contentType == null) {
                contentType = "";
            }
            int semicolon = contentType.indexOf(';');
            if (semicolon >= 0) {
                contentType = contentType.substring(0, semicolon).trim();
            } else {
                contentType = contentType.trim();
            }

            if ("multipart/form-data".equals(contentType)) {
                parseParts(false);
                success = true;
                return;
            }

            if( !getConnector().isParseBodyMethod(getMethod()) ) {
                success = true;
                return;
            }

            if (!("application/x-www-form-urlencoded".equals(contentType))) {
                success = true;
                return;
            }

            // 获取content-length,如果chunked,则返回-1
            int len = getContentLength();

            // len大于0则是普通的body
            if (len > 0) {
                int maxPostSize = connector.getMaxPostSize();
                // 检查body的大小是否超过了maxPostSize的大小,maxPostSize默认是2m
                if ((maxPostSize >= 0) && (len > maxPostSize)) {
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.postTooLarge"));
                    }
                    checkSwallowInput();
                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                    return;
                }
                // body字节部分
                byte[] formData = null;
                // 如果body的大小小于CACHED_POST_LEN,默认8192及8k,tomcat是用缓存的字节数组,不是重新创建一个,新建一个意味着向os申请一块
                // 连续的内存,如果不重用,则会出现频繁的os申请
                if (len < CACHED_POST_LEN) {
                    if (postData == null) {
                        postData = new byte[CACHED_POST_LEN];
                    }
                    formData = postData;
                } else {
                    formData = new byte[len];
                }
                try {
                    // 这里是从inputbuffer里读数据.
                    // GO
                    if (readPostBody(formData, len) != len) {
                        parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
                        return;
                    }
                } catch (IOException e) {
                    // Client disconnect
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"), e);
                    }
                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                    return;
                }
                parameters.processParameters(formData, 0, len);
            } else if ("chunked".equalsIgnoreCase(
                    coyoteRequest.getHeader("transfer-encoding"))) {
                byte[] formData = null;
                try {
                    formData = readChunkedPostBody();
                } catch (IllegalStateException ise) {
                    // chunkedPostTooLarge error
                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"),
                                ise);
                    }
                    return;
                } catch (IOException e) {
                    // Client disconnect
                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"), e);
                    }
                    return;
                }
                if (formData != null) {
                    parameters.processParameters(formData, 0, formData.length);
                }
            }
            success = true;
        } finally {
            if (!success) {
                parameters.setParseFailedReason(FailReason.UNKNOWN);
            }
        }

    }
  • parameters.handleQueryParameters()解析url后面的参数
  • 判断是否执行过getInputStream(),如果通过stream流的形式读了,则在getParameter是读不到body里的参数的。只能获取url后面的参数
if (usingInputStream || usingReader) {
     success = true;
     return;
}
  • 检查header content-type,如果不是multipart/form-data 或者
    application/x-www-form-urlencoded 的,则不解析body
            String contentType = getContentType();
            if (contentType == null) {
                contentType = "";
            }
            int semicolon = contentType.indexOf(';');
            if (semicolon >= 0) {
                contentType = contentType.substring(0, semicolon).trim();
            } else {
                contentType = contentType.trim();
            }

            if ("multipart/form-data".equals(contentType)) {
                parseParts(false);
                success = true;
                return;
            }

            if( !getConnector().isParseBodyMethod(getMethod()) ) {
                success = true;
                return;
            }

            if (!("application/x-www-form-urlencoded".equals(contentType))) {
                success = true;
                return;
            }
  • 开始解析请求体
    • 如果getContentLength()大于0,先检查请求体大小是否超过最大值,超过了返回;没超过,开始解析readPostBody(formData, len)
         if (len > 0) {
               int maxPostSize = connector.getMaxPostSize();
               // 检查body的大小是否超过了maxPostSize的大小,maxPostSize默认是2m
               if ((maxPostSize >= 0) && (len > maxPostSize)) {
                   Context context = getContext();
                   if (context != null && context.getLogger().isDebugEnabled()) {
                       context.getLogger().debug(
                               sm.getString("coyoteRequest.postTooLarge"));
                   }
                   checkSwallowInput();
                   parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                   return;
               }
               // body字节部分
               byte[] formData = null;
               // 如果body的大小小于CACHED_POST_LEN,默认8192及8k,tomcat是用缓存的字节数组,不是重新创建一个,新建一个意味着向os申请一块
               // 连续的内存,如果不重用,则会出现频繁的os申请
               if (len < CACHED_POST_LEN) {
                   if (postData == null) {
                       postData = new byte[CACHED_POST_LEN];
                   }
                   formData = postData;
               } else {
                   formData = new byte[len];
               }
               try {
                   // 这里是从inputbuffer里读数据.
                   // GO
                   if (readPostBody(formData, len) != len) {
                       parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
                       return;
                   }
               } catch (IOException e) {
                   // Client disconnect
                   Context context = getContext();
                   if (context != null && context.getLogger().isDebugEnabled()) {
                       context.getLogger().debug(
                               sm.getString("coyoteRequest.parseParameters"), e);
                   }
                   parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                   return;
               }
               parameters.processParameters(formData, 0, len);
           }
  • chunked模式解析,readChunkedPostBody()
          else if ("chunked".equalsIgnoreCase(
                    coyoteRequest.getHeader("transfer-encoding"))) {
                byte[] formData = null;
                try {
                    formData = readChunkedPostBody();
                } catch (IllegalStateException ise) {
                    // chunkedPostTooLarge error
                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"),
                                ise);
                    }
                    return;
                } catch (IOException e) {
                    // Client disconnect
                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                    Context context = getContext();
                    if (context != null && context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"), e);
                    }
                    return;
                }
                if (formData != null) {
                    parameters.processParameters(formData, 0, formData.length);
                }
            }
            success = true;
        }
三、IdentityFilter请求体
1、org.apache.catalina.connector.Request#readPostBody
   protected int readPostBody(byte[] body, int len)
            throws IOException {

        int offset = 0;
        do {
            // 这里最终是Http11InputBuffer根据activeFilter去读body
            // GO
            int inputLen = getStream().read(body, offset, len - offset);
            if (inputLen <= 0) {
                return offset;
            }
            offset += inputLen;
        } while ((len - offset) > 0);
        return len;

    }
  • while循环读取,将读取的数据放入body
  • 读取的其实位置为offset,读取的长度为len-offset,从而控制读取的数据是请求体的数据
2、 CoyoteInputStream#read
    public int read(final byte[] b, final int off, final int len) throws IOException {
        checkNonBlockingRead();

        if (SecurityUtil.isPackageProtectionEnabled()) {
            try {
                Integer result = AccessController
                        .doPrivileged(new PrivilegedExceptionAction<Integer>() {

                            @Override
                            public Integer run() throws IOException {
                                Integer integer = Integer.valueOf(ib.read(b, off, len));
                                return integer;
                            }

                        });
                return result.intValue();
            } catch (PrivilegedActionException pae) {
                Exception e = pae.getException();
                if (e instanceof IOException) {
                    throw (IOException) e;
                } else {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        } else {
            return ib.read(b, off, len);
        }
    }
3、 InputBuffer#read
    public int read(byte[] b, int off, int len) throws IOException {
        throwIfClosed();

        if (checkByteBufferEof()) {
            return -1;
        }
        int n = Math.min(len, bb.remaining());
        bb.get(b, off, n);
        return n;
    }
4、 InputBuffer#checkByteBufferEof
    private boolean checkByteBufferEof() throws IOException {
        if (bb.remaining() == 0) {
            int n = realReadBytes();
            if (n < 0) {
                return true;
            }
        }
        return false;
    }
5、 InputBuffer#realReadBytes
   public int realReadBytes() throws IOException {
        if (closed) {
            return -1;
        }
        if (coyoteRequest == null) {
            return -1;
        }

        if (state == INITIAL_STATE) {
            state = BYTE_STATE;
        }

        try {
            return coyoteRequest.doRead(this);
        } catch (IOException ioe) {
            coyoteRequest.setErrorException(ioe);
            // An IOException on a read is almost always due to
            // the remote client aborting the request.
            throw new ClientAbortException(ioe);
        }
    }
6、 org.apache.coyote.Request#doRead
    public int doRead(ApplicationBufferHandler handler) throws IOException {
        if (getBytesRead() == 0 && !response.isCommitted()) {
            action(ActionCode.ACK, ContinueResponseTiming.ON_REQUEST_BODY_READ);
        }

        int n = inputBuffer.doRead(handler);
        if (n > 0) {
            bytesRead+=n;
        }
        return n;
    }

7、 Http11InputBuffer#doRead
    public int doRead(ApplicationBufferHandler handler) throws IOException {
        if (lastActiveFilter == -1) {
            return inputStreamInputBuffer.doRead(handler);
        } else {
            return activeFilters[lastActiveFilter].doRead(handler);
        }
    }
8、 IdentityInputFilter#doRead
    public int doRead(ApplicationBufferHandler handler) throws IOException {

        int result = -1;

        // contentLength,remaining是在filter设置request的时候确定的
        if (contentLength >= 0) {
            if (remaining > 0) {
                // 这里又是buffer,这个buffer是在设置activeFilter时确定的,一个filter的话,该buffer就是SocketInputBuffer,handler是
                // ApplicationBufferHandler
                int nRead = buffer.doRead(handler);
                if (nRead > remaining) {
                    // The chunk is longer than the number of bytes remaining
                    // in the body; changing the chunk length to the number
                    // of bytes remaining
                    handler.getByteBuffer().limit(handler.getByteBuffer().position() + (int) remaining);
                    result = (int) remaining;
                } else {
                    result = nRead;
                }
                if (nRead > 0) {
                    remaining = remaining - nRead;
                }
            } else {
                // No more bytes left to be read : return -1 and clear the
                // buffer
                if (handler.getByteBuffer() != null) {
                    handler.getByteBuffer().position(0).limit(0);
                }
                result = -1;
            }
        }
        return result;
    }
四、chunked模式解析请求体
1、org.apache.catalina.connector.Request#readChunkedPostBody
   protected byte[] readChunkedPostBody() throws IOException {
        ByteChunk body = new ByteChunk();

        byte[] buffer = new byte[CACHED_POST_LEN];

        int len = 0;
        while (len > -1) {
            len = getStream().read(buffer, 0, CACHED_POST_LEN);
            if (connector.getMaxPostSize() >= 0 &&
                    (body.getLength() + len) > connector.getMaxPostSize()) {
                // Too much data
                checkSwallowInput();
                throw new IllegalStateException(
                        sm.getString("coyoteRequest.chunkedPostTooLarge"));
            }
            if (len > 0) {
                body.append(buffer, 0, len);
            }
        }
        if (body.getLength() == 0) {
            return null;
        }
        if (body.getLength() < body.getBuffer().length) {
            int length = body.getLength();
            byte[] result = new byte[length];
            System.arraycopy(body.getBuffer(), 0, result, 0, length);
            return result;
        }

        return body.getBuffer();
    }
  • 进入到CoyoteInputStream#read
  • 最终是进入到ChunkedInputFilter#doRead
2、ChunkedInputFilter#doRead
 public int doRead(ApplicationBufferHandler handler) throws IOException {
        if (endChunk) {
            return -1;
        }

        checkError();

        if(needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }

        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }

        int result = 0;

        if (readChunk == null || readChunk.position() >= readChunk.limit()) {
            if (readBytes() < 0) {
                throwIOException(sm.getString("chunkedInputFilter.eos"));
            }
        }

        if (remaining > readChunk.remaining()) {
            result = readChunk.remaining();
            remaining = remaining - result;
            if (readChunk != handler.getByteBuffer()) {
                handler.setByteBuffer(readChunk.duplicate());
            }
            readChunk.position(readChunk.limit());
        } else {
            result = remaining;
            if (readChunk != handler.getByteBuffer()) {
                handler.setByteBuffer(readChunk.duplicate());
                handler.getByteBuffer().limit(readChunk.position() + remaining);
            }
            readChunk.position(readChunk.position() + remaining);
            remaining = 0;
            //we need a CRLF
            if ((readChunk.position() + 1) >= readChunk.limit()) {
                //if we call parseCRLF we overrun the buffer here
                //so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                parseCRLF(false); //parse the CRLF immediately
            }
        }

        return result;
    }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,928评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,748评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,282评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,065评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,101评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,855评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,521评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,414评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,931评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,053评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,191评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,873评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,529评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,074评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,188评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,491评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,173评论 2 357

推荐阅读更多精彩内容