记一次由gzip引起的nginx转发事故

news2024/11/24 18:41:15

故事背景

书接前几篇文章,仍然是交付甲方遇到的一个特殊诉求,从而引发了本期的事故。甲方的诉求是前端的请求过来,需要加密,但是要经过waf,必须要求是请求明文,那就要在waf和nginx之间做一个解密前置应用处理。大致架构图如下:
在这里插入图片描述
本次事故的起因是因为,经过waf的请求响应头信息增加了一个Content-Encoding:gzip导致的数据无法返回前端。

技术栈

nginx:1.16.1
springboot:2.5.14
hutool:5.8.15

NGINX下载:NGINX下载链接

情景再现

我们一点一点还原下,当时遇到的问题。我们这里需要两个java应用和一个nginx三个工程。注意下述不是完整代码,都是核心代码片段。只为说明问题产生的过程,所以不会大面积贴出所有代码!!!

前置服务

http调用目标服务核心代码

注意:此处代码重点在于使用hutool HttpRequest.execute()调用目标服务,其余方法理性观看!

public static HttpResponse executeAndGetResponse(HttpServletRequest request, String forwardAddr, String decryptData, String wafHost) {
        if (decryptData == null) {
            return executeAndGetResponse(request, forwardAddr, wafHost);
        }
        HttpRequest httpRequest = getHttpRequest(request, forwardAddr, wafHost);
        Map<String, Object> copyForm = null;
        if (httpRequest.form() != null) {
            copyForm = new HashMap<>(httpRequest.form());
        }
        
        httpRequest.body(decryptData, request.getContentType());
        //重新设置form,设置body时会将form设为null,所以需重新设置form
        //TODO body和form二者只能存在一个,当form存在时,则body会被置为null,除了Get请求其他参数都要放到body体中
        if (Objects.equals(RequestMethodEnum.GET.getMethod(), request.getMethod())
                && httpRequest.form() == null && CollectionUtil.isNotEmpty(copyForm)) {
            httpRequest.form(copyForm);
        }
        HttpResponse response = httpRequest.execute();
        return response;
    }

响应流返回给ng核心代码

public static void returnStream(HttpResponse forwardResponse, HttpServletResponse response) {
        //拷贝转发响应头携带的信息
        Map<String, List<String>> headers = forwardResponse.headers();
        if (CollectionUtil.isNotEmpty(headers)) {
            log.info("----------------------------------------------");
            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
                if (CollectionUtil.isNotEmpty(entry.getValue())) {
                    response.addHeader(entry.getKey(), entry.getValue().get(0));
                    log.info("响应头信息:[{}:{}]", entry.getKey(), entry.getValue().get(0));
                } else {
                    response.addHeader(entry.getKey(), "");
                }
            }
        }
        //输出响应日志
        String contentType = forwardResponse.header("Content-Type");
        if (StringUtils.isNotBlank(contentType) && contentType.toLowerCase().startsWith(MediaType.APPLICATION_JSON_VALUE)) {
            String body = forwardResponse.body();
            if (body.length() > 1000) {
                log.info("请求响应长度大于1000,只打印前1000个字符,详情可查看转发服务:{}", StrUtil.cleanBlank(body).substring(0, 1000));
            } else {
                log.info("请求响应:{}", StrUtil.cleanBlank(body));
            }
        }
        OutputStream outputStream = null;
        GZIPOutputStream gzipOut = null;
        try {
            response.setCharacterEncoding("UTF-8");

            outputStream = response.getOutputStream();
            IoUtil.copy(forwardResponse.bodyStream(), outputStream);
            outputStream.flush();
        } catch (IOException e) {
            log.error("流读取IO异常", e);
            throw new RuntimeException(e);
        } finally {
            if (gzipOut != null) {
                try {
                    gzipOut.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

启动配置文件

spring.application.name=router
server.port=7070

waf.host=127.0.0.1
forward.url=http://${waf.host}:9090

目标服务

接口实现

import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPOutputStream;

@RestController
@RequestMapping("/hello")
@Slf4j
public class HelloController {

    @GetMapping("/test")
    public Map getTest() {
        Map<String, String> ng = new HashMap<>();
        ng.put("2", "333333333333333333333");
        return ng;
    }

    @GetMapping("/test3")
    public void getTest3(String a, String b, HttpServletResponse response) {
        Map<String, String> ng = new HashMap<>();
        ng.put("2", "Hello,received:" + a + b);

        response.addHeader("Content-Encoding", "gzip");

        OutputStream outputStream = null;
        GZIPOutputStream gzipOut = null;
        try {
            response.setCharacterEncoding("UTF-8");

            outputStream = response.getOutputStream();
            log.info("--响应开始压缩--");
            gzipOut = new GZIPOutputStream(outputStream);

            IoUtil.write(gzipOut, false, JSON.toJSONBytes(ng));
            gzipOut.flush();
            gzipOut.finish();
            log.info("--响应压缩完成--");

            outputStream.flush();
        } catch (IOException e) {
            log.error("流读取IO异常", e);
            throw new RuntimeException(e);
        } finally {
            if (gzipOut != null) {
                try {
                    gzipOut.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }


    @GetMapping("/test2")
    public Map getTest2() {
        Map<String, String> ng = new HashMap<>();
        ng.put("2", "333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333");
        ng.put("3", "333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333");
        return ng;
    }

    @GetMapping("/gzip-test")
    public void gzipTest(HttpServletRequest request, HttpServletResponse response) {
        response.addHeader("Content-Encoding", "gzip");

        OutputStream outputStream = null;
        GZIPOutputStream gzipOut = null;
        try {
            response.setCharacterEncoding("UTF-8");

            outputStream = response.getOutputStream();
            log.info("--响应开始压缩--");
            gzipOut = new GZIPOutputStream(outputStream);

            Map<String, String> data = new HashMap<>();
            data.put("name", "Kevin");
            IoUtil.write(gzipOut, false, JSON.toJSONBytes(data));
            gzipOut.flush();
            gzipOut.finish();
            log.info("--响应压缩完成--");

            outputStream.flush();
        } catch (IOException e) {
            log.error("流读取IO异常", e);
            throw new RuntimeException(e);
        } finally {
            if (gzipOut != null) {
                try {
                    gzipOut.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    @GetMapping("/gzip-test2")
    public void gzipTest2(HttpServletRequest request, HttpServletResponse response) {
        response.addHeader("Content-Encoding", "gzip");

        OutputStream outputStream = null;
        GZIPOutputStream gzipOut = null;
        try {
            response.setCharacterEncoding("UTF-8");

            outputStream = response.getOutputStream();
            log.info("--响应开始压缩--");
            gzipOut = new GZIPOutputStream(outputStream);

            Map<String, String> data = new HashMap<>();
            data.put("name", "Mary");
            IoUtil.write(gzipOut, false, JSON.toJSONBytes(data));
            gzipOut.flush();
            gzipOut.finish();
            log.info("--响应压缩完成--");

            outputStream.flush();
        } catch (IOException e) {
            log.error("流读取IO异常", e);
            throw new RuntimeException(e);
        } finally {
            if (gzipOut != null) {
                try {
                    gzipOut.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

启动配置文件

spring.application.name=demo2
server.port=9090

nginx配置


#user  nobody;
worker_processes  1;

error_log  logs/error.log;
#error_log  logs/error.log  notice;
error_log  logs/error.log  info;

pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    upstream backend {
        server 127.0.0.1:7070;
        keepalive 50;
    }

    #gzip  on;

    server {
        listen       6060;
        server_name  127.0.0.1;

        access_log  logs/host.access.log  main;
        client_max_body_size 20m;
        client_header_buffer_size 32k;

        location / {
            proxy_pass http://backend;
            #proxy_http_version 1.1;
            #proxy_set_header Connection "";
        }
    }

}

问题集锦

错误1:NS_ERROR_NET_RESET

在这里插入图片描述

解决方案

增加配置
proxy_http_version 1.1;
proxy_set_header Connection “”;


#user  nobody;
worker_processes  1;

error_log  logs/error.log;
#error_log  logs/error.log  notice;
error_log  logs/error.log  info;

pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    upstream backend {
        server 127.0.0.1:7070;
        keepalive 50;
    }

    #gzip  on;

    server {
        listen       6060;
        server_name  127.0.0.1;

        access_log  logs/host.access.log  main;
        client_max_body_size 20m;
        client_header_buffer_size 32k;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }

}

错误2:net::ERR_CONTENT_DECODING_FAILED 200 (OK)

在这里插入图片描述

解决方案

修改前置应用返回ng的响应流处理方法,检测到响应头中含有Content-Encoding:gzip对报文内容做压缩处理,再返给ng

public static void returnStream(HttpResponse forwardResponse, HttpServletResponse response) {
        //拷贝转发响应头携带的信息
        Map<String, List<String>> headers = forwardResponse.headers();
        if (CollectionUtil.isNotEmpty(headers)) {
            log.info("----------------------------------------------");
            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
                if (CollectionUtil.isNotEmpty(entry.getValue())) {
                    response.addHeader(entry.getKey(), entry.getValue().get(0));
                    log.info("响应头信息:[{}:{}]", entry.getKey(), entry.getValue().get(0));
                } else {
                    response.addHeader(entry.getKey(), "");
                }
            }
        }
        //输出响应日志
        String contentType = forwardResponse.header("Content-Type");
        if (StringUtils.isNotBlank(contentType) && contentType.toLowerCase().startsWith(MediaType.APPLICATION_JSON_VALUE)) {
            String body = forwardResponse.body();
            if (body.length() > 1000) {
                log.info("请求响应长度大于1000,只打印前1000个字符,详情可查看转发服务:{}", StrUtil.cleanBlank(body).substring(0, 1000));
            } else {
                log.info("请求响应:{}", StrUtil.cleanBlank(body));
            }
        }
        OutputStream outputStream = null;
        GZIPOutputStream gzipOut = null;
        try {
            response.setCharacterEncoding("UTF-8");

            outputStream = response.getOutputStream();

            String contentEncoding = forwardResponse.contentEncoding();
            if(StringUtils.isNotBlank(contentEncoding) && contentEncoding.equalsIgnoreCase("gzip")) {
                log.info("--响应开始压缩--");
                gzipOut = new GZIPOutputStream(outputStream);
                IoUtil.write(gzipOut, false, forwardResponse.bodyBytes());
                gzipOut.flush();
                gzipOut.finish();
                log.info("--响应压缩完成--");
            } else {
                IoUtil.write(outputStream, false, forwardResponse.bodyBytes());
                outputStream.flush();
            }
        } catch (IOException e) {
            log.error("流读取IO异常", e);
            throw new RuntimeException(e);
        } finally {
            if (gzipOut != null) {
                try {
                    gzipOut.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

高版本Nginx

最开始我本地复现并没有关注到ng的版本,所以开始使用的是1.25.3的版本,还衍生出了新的问题也贴出来供大家参考下。


#user  nobody;
worker_processes  1;

error_log  logs/error.log;
error_log  logs/error.log  notice;
error_log  logs/error.log  info;

pid        logs/nginx.pid;


events {
    worker_connections  10000;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    tcp_nopush     on;

    keepalive_timeout  65;

    gzip  on;

    upstream backend {
        server 127.0.0.1:7070;
        keepalive 50;
    }

    server {
        listen       6060;
        server_name  127.0.0.1;

        access_log  logs/host.access.log  main;
        client_max_body_size 20m;
        client_header_buffer_size 32k;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

}

错误1:502 Bad Gateway

在这里插入图片描述

解决方案

通过配置ng的错误日志可以查到如下错误:

2024/03/28 01:16:07 [error] 8352#5480: *43 upstream sent duplicate header line: "Transfer-Encoding: chunked", previous value: "Transfer-Encoding: chunked" while reading response header from upstream, client: 127.0.0.1, server: 127.0.0.1, request: "GET /noauth/captcha/slide-image HTTP/1.1", upstream: "http://127.0.0.1:7070/noauth/captcha/slide-image", host: "localhost:6060"

意思是请求头中行Transfer-Encoding: chunked重复导致的;
这个只需要在后端服务中返回ng之前移除请求头就可以了。
注意:这是因为proxy_http_version 1.1;
proxy_set_header Connection “”; 这两个配置导致的,高版本nginx中会自动加入Transfer-Encoding: chunked,所以从后端传过来的response中也存在就会重复,nginx1.16.1版本就不会出现这个问题。
在这里插入图片描述

参考

upstream sent duplicate header line: “Transfer-Encoding: chunked”

总结

一个gzip引发的案件,原因是因为过waf的时候,waf会自动引入gzip压缩处理,导致前置应用没有处理,解决此问题的方案有2。

方案一

前置应用获取到目标服务的响应结果后,已经是解压后的数据,这是因为hutool是一个http客户端,如果服务端返回的response中带有gzip的标志,hutool获取到的结果已经是解压过后的数据,可以继续移除hutool获取到的响应头中的Content-Encoding:gzip往外继续抛即可,这样抛到ng的时候也是不带gzip头信息的,数据也刚好搭对。但是注意这就失去了压缩的意义了,会损失一些传输损耗,达不到压缩的积极意义。

方案二

那就是检测到响应头中带有gzip标识,返回响应流的时候,做压缩处理,同样是响应头和响应体搭对即可。

未解之谜

问题

当我们解决完上面ng的问题之后,发现一个很奇特的现象是我们刷新的第一个接口有数据返回,但是后续的接口却没有数据,http请求甚至没有返回响应码给前端!

原因

我们的代码是这样写的,我们使用了finish方法,而没有使用flush方法

gzipOut = new GZIPOutputStream(outputStream);
IoUtil.write(gzipOut, false, forwardResponse.bodyBytes());
gzipOut.finish();

我们看到GZIPOutputStream的构造函数是这样的,恍然大悟

/**
 * Creates a new output stream with a default buffer size.
 *
 * <p>The new output stream instance is created as if by invoking
 * the 2-argument constructor GZIPOutputStream(out, false).
 *
 * @param out the output stream
 * @exception IOException If an I/O error has occurred.
 */
public GZIPOutputStream(OutputStream out) throws IOException {
    this(out, 512, false);
}

追溯到他的父类,发现了问题,512byte的数组,可以认为是缓冲区,如果不进行flush是不会写出数据。

/**
 * Creates a new output stream with the specified compressor,
 * buffer size and flush mode.

 * @param out the output stream
 * @param def the compressor ("deflater")
 * @param size the output buffer size
 * @param syncFlush
 *        if {@code true} the {@link #flush()} method of this
 *        instance flushes the compressor with flush mode
 *        {@link Deflater#SYNC_FLUSH} before flushing the output
 *        stream, otherwise only flushes the output stream
 *
 * @throws IllegalArgumentException if {@code size <= 0}
 *
 * @since 1.7
 */
public DeflaterOutputStream(OutputStream out,
                            Deflater def,
                            int size,
                            boolean syncFlush) {
    super(out);
    if (out == null || def == null) {
        throw new NullPointerException();
    } else if (size <= 0) {
        throw new IllegalArgumentException("buffer size <= 0");
    }
    this.def = def;
    this.buf = new byte[size];
    this.syncFlush = syncFlush;
}

经过上面追查,我们恍然大悟,我们调用的第一个接口是图形验证码,数据量早就超过了512,但是后续的接口却没这么幸运!至此,我们遇到的所有问题都已经解决。并且我们发现,finish方法是不需要调用的,如果调用了close方法就会自己执行finish方法。

public void close() throws IOException {
    if (!closed) {
        try {
        	finish();
        } finally {
	        if (usesDefaultDeflater)
	            def.end();
	        }
	        out.close();
	        closed = true;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1557881.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【3】3道链表力扣题:删除链表中的节点、反转链表、判断一个链表是否有环

3道链表力扣题 一、删除链表中的节点&#x1f30f; 题目链接&#x1f4d5; 示例&#x1f340; 分析&#x1f4bb; 代码 二、反转链表&#x1f30f; 题目链接&#x1f4d5; 示例&#x1f340; 分析① 递归② 迭代 三、判断一个链表是否有环&#x1f30f; 题目链接&#x1f4d5; …

学习transformer模型-Input Embedding 嵌入层的简明介绍

今天介绍transformer模型的Input Embedding 嵌入层。 背景 嵌入层的目标是使模型能够更多地了解单词、标记或其他输入之间的关系。 从头开始嵌入Embeddings from Scratch 嵌入序列需要分词器tokenizer、词汇表和索引&#xff0c;以及词汇表中每个单词的三维嵌入。Embedding a s…

git clone 后如何 checkout 到 remote branch

what/why 通常情况使用git clone github_repository_address下载下来的仓库使用git branch查看当前所有分支时只能看到master分支&#xff0c;但是想要切换到其他分支进行工作怎么办❓ 其实使用git clone下载的repository没那么简单&#x1f625;&#xff0c;clone得到的是仓库…

一个 hipsolver 特征值示例

1&#xff0c;原理 通过雅可比旋转&#xff0c;对对称矩阵计算特征值和特征向量&#xff1b; 通过初等正交变换&#xff0c;每次把其中一个非主对角元素消成零&#xff0c;最终只剩主对角线非零元素为特征值&#xff0c;同时把初等变换累积下来&#xff0c;构成特征向量。 2&a…

使用1panel部署Ollama WebUI(dcoekr版)浅谈

文章目录 说明配置镜像加速Ollama WebUI容器部署Ollama WebUI使用问题解决&#xff1a;访问页面空白 说明 1Panel简化了docker的部署&#xff0c;提供了可视化的操作&#xff0c;但是我在尝试创建Ollama WebUI容器时&#xff0c;遇到了从github拉取镜像网速很慢的问题&#xf…

Java反射系列(3):从spring反射工具ReflectionUtils说起

传送门 在比较早的时候&#xff0c;就讨论过java反射的一些用法及概念&#xff1a; Java反射系列(1)&#xff1a;入门基础 以及反射的基石Class对象&#xff01; Java反射系列(2)&#xff1a;从Class获取父类方法说起 今天就从工作中实际的例子来看看反射的应用。 兼容…

pytest--python的一种测试框架--request请求加入headers

一、request headers中的cookie和session机制的作用与区别 Cookie 和 Session 是两种在客户端和服务器之间保持状态的技术。HTTP 协议本身是无状态的&#xff0c;这意味着服务器无法从上一次的请求中保留任何信息到下一次请求。Cookie 和 Session 机制就是为了解决这个问题。 …

240330-大模型资源-使用教程-部署方式-部分笔记

A. 大模型资源 Models - Hugging FaceHF-Mirror - Huggingface 镜像站模型库首页 魔搭社区 B. 使用教程 HuggingFace HuggingFace 10分钟快速入门&#xff08;一&#xff09;&#xff0c;利用Transformers&#xff0c;Pipeline探索AI。_哔哩哔哩_bilibiliHuggingFace快速入…

遥感数字图像处理的学习笔记

相关链接&#xff1a; 遥感数字图像处理实验教程&#xff08;韦玉春&#xff09;--部分实验问题回答 目录 1.什么是图像&#xff0c;什么是数字图像&#xff1f; 2.什么是遥感数字图像&#xff1f;模拟图像(照片)与遥感数字图像有什么区别&#xff1f; 3.什么是遥感数字图像…

小小狠招:巧妙使用HANA数据库的jdbc driver

SAP旗下的HANA数据库&#xff0c;实际上是分为两个系列进行发布&#xff0c;一种是基于本地部署的称之为HANA Platform。另一种是面向Cloud平台的&#xff0c;称之为HANA Cloud。 在实际使用当用&#xff0c;因为两者基本上共用同一代码库&#xff0c;除个别地方略有差异以外&…

C++要学到什么程度才能找到实习?

在考虑 C 学习到何种程度可以找到实习时&#xff0c;以下是一些具体的方向和建议。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我…

HarmonyOS 应用开发之模型切换

本文介绍如何将一个FA模型开发的声明式范式应用切换到Stage模型&#xff0c;您需要完成如下动作&#xff1a; 工程切换&#xff1a;新建一个Stage模型的应用工程。 配置文件切换&#xff1a;config.json切换为app.json5和module.json5。 组件切换&#xff1a;PageAbility/Serv…

如何计算KST指标,昂首资本一个公式计算

在上一篇文章中&#xff0c;Anzo Capital昂首资本和各位投资者一起了解了KST指标&#xff0c;今天我们继续分享如何计算KST指标。 首先投资者可以在时间范围9、12、18和24分析变化率值。 前三个值(时间帧9、12、18)用EMA 26平滑&#xff0c;最后一个值用EMA 39平滑。 然后&…

AndroidStudio出现类似 Could not create task ‘:app:ToolOperatorDemo.main()‘. 错误

先看我们的报错 翻译过来大概意思是:无法创建任务:app:ToolOperatorDemo.main()。 没有找到名称为“main”的源集。 解决方法&#xff1a; 在.idea文件夹下的gradle.xml文件中 <GradleProjectSettings>标签下添加<option name"delegatedBuild" value"f…

前端小白如何理解mvc mvp mvvm

架构、框架、设计模式是都是啥&#xff1f; 架构&#xff1a;抽象出来不同组织或者对象亦或是简单组件&#xff0c;根据需求和各个单元的功能&#xff0c;进行组合排列。 从而完成系统的运行或者是实现目标。 框架&#xff1a;使用什么样的规则&#xff0c;什么样的开发语言&…

政安晨:【Keras机器学习实践要点】(十)—— 自定义保存和序列化

目录 导言 涵盖的API Setup 状态保存自定义 构建和编译保存自定义 结论 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在…

2024年京东云主机租用价格_京东云服务器优惠价格表

2024年京东云服务器优惠价格表&#xff0c;轻量云主机优惠价格5.8元1个月、轻量云主机2C2G3M价格50元一年、196元三年&#xff0c;2C4G5M轻量云主机165元一年&#xff0c;4核8G5M云主机880元一年&#xff0c;游戏联机服务器4C16G配置26元1个月、4C32G价格65元1个月、8核32G费用…

速腾聚创上市后首份财报:冲击年销百万台,押注人形机器人

作者 |老缅 编辑 |德新 港股「激光雷达第一股」速腾聚创&#xff0c;交出了上市后的首份业绩报告。 3月27日&#xff0c;速腾聚创发布了2023年度财报。 报告期内&#xff0c;公司迎来高速的业务增长——2023年总收入达到人民币11.2亿元&#xff0c;同比增长达到111.2%。这主…

Artplayer视频JSON解析播放器源码|支持弹幕|json数据模式

全开源Artplayer播放器视频解析源码&#xff0c;支持两种返回模式&#xff1a;网页播放模式、json数据模式&#xff0c;json数据模式支持限制ip每分钟访问次数UA限制key密钥&#xff0c;也可理解为防盗链 &#xff0c;本播放器带弹幕库。 运行环境 推荐使用PHP8.0 redis扩展…

书生 浦语大模型全链路开源体系

通用大模型成为发展通用人工智能的重要途径 书生 浦语大模型的开源历程 书生 浦语 2.0体系&#xff0c;面向不同的使用需求&#xff0c;每个规格包含三个模型版本&#xff0c;&#xff08;7B、20B&#xff09;InternLM2-Base、InternLM2、InternLM2-Chat。 大模型是回归语言建…