hav-cs50-merge-02

哈佛 CS50 中文官方笔记(三)

第七讲

原文:cs50.harvard.edu/x/notes/7/

  • 欢迎!

  • 平面文件数据库

  • 关系数据库

  • SELECT

  • INSERT

  • DELETE

  • UPDATE

  • IMDb

  • JOIN

  • 索引

  • 在 Python 中使用 SQL

  • 竞争条件

  • SQL 注入攻击

  • 总结

欢迎!

  • 在前几周,我们向您介绍了 Python,这是一种高级编程语言,它使用了我们在 C 语言中学到的相同构建块。然而,我们引入这种新语言不是为了学习“另一种语言”。相反,我们这样做是因为某些工具更适合某些工作,而不太适合其他工作!

  • 这周,我们将继续学习更多与 Python 相关的语法。

  • 此外,我们将把这种知识与我们所学的内容结合起来。

  • 最后,我们将讨论SQL结构化查询语言,这是一种特定领域的方法,我们可以通过它来交互和修改数据。

  • 总体而言,本课程的一个目标就是学习编程的一般知识——而不仅仅是学习本课程中描述的语言的编程。

平面文件数据库

  • 如你所见,数据通常可以用列和行的模式来描述。

  • 类似于在 Microsoft Excel 和 Google Sheets 中创建的电子表格可以输出为csv逗号分隔值文件。

  • 如果你查看一个csv文件,你会注意到文件是平的,因为我们的所有数据都存储在一个由文本文件表示的单个表中。我们称这种形式的数据为平面文件数据库

  • 所有数据都是按行存储的。每个列由逗号或其他值分隔。

  • Python 自带对csv文件的原生支持。

  • 首先,下载favorites.csv并将其上传到cs50.dev中的文件资源管理器内。其次,检查这些数据,注意第一行是特殊的,因为它定义了每一列。然后,每条记录按行存储。

  • 在你的终端窗口中,输入code favorites.py并编写以下代码:

    # Prints all favorites in CSV using csv.reader 
    import csv
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create reader
        reader = csv.reader(file)
    
        # Skip header row
        next(reader)
    
        # Iterate over CSV file, printing each favorite
        for row in reader:
            print(row[1]) 
    

    注意到已经导入了csv库。此外,我们创建了一个reader,它将保存csv.reader(file)的结果。csv.reader函数从文件中读取每一行,在我们的代码中,我们将结果存储在reader中。因此,print(row[1])将打印出favorites.csv文件中的语言。

  • 你可以按照以下方式改进你的代码:

    # Stores favorite in a variable 
    import csv
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create reader
        reader = csv.reader(file)
    
        # Skip header row
        next(reader)
    
        # Iterate over CSV file, printing each favorite
        for row in reader:
            favorite = row[1]
            print(favorite) 
    

    注意favorite被存储并打印出来。此外,注意我们使用next函数跳到读者下一行。

  • 上述方法的一个缺点是我们信任row[1]始终是首选。然而,如果列被移动了,会发生什么呢?

  • 我们可以修复这个潜在的问题。Python 还允许你通过列表的键进行索引。按照以下方式修改你的代码:

    # Prints all favorites in CSV using csv.DictReader 
    import csv
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create DictReader
        reader = csv.DictReader(file)
    
        # Iterate over CSV file, printing each favorite
        for row in reader:
            favorite = row["language"]
            print(favorite) 
    

    注意到这个例子直接在打印语句中使用了language键。favorite索引到row["language"]reader字典。

  • 这可以进一步简化为:

    # Prints all favorites in CSV using csv.DictReader 
    import csv
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create DictReader
        reader = csv.DictReader(file)
    
        # Iterate over CSV file, printing each favorite
        for row in reader:
            print(row["language"]) 
    
  • 要统计在csv文件中表达的首选语言的数目,我们可以这样做:

    # Counts favorites using variables 
    import csv
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create DictReader
        reader = csv.DictReader(file)
    
        # Counts
        scratch, c, python = 0, 0, 0
    
        # Iterate over CSV file, counting favorites
        for row in reader:
            favorite = row["language"]
            if favorite == "Scratch":
                scratch += 1
            elif favorite == "C":
                c += 1
            elif favorite == "Python":
                python += 1
    
    # Print counts print(f"Scratch: {scratch}")
    print(f"C: {c}")
    print(f"Python: {python}") 
    

    注意到每种语言都是通过if语句进行统计的。此外,注意那些if语句中的双等号==

  • Python 允许我们使用字典来统计每种语言的counts。考虑以下对我们代码的改进:

    # Counts favorites using dictionary 
    import csv
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create DictReader
        reader = csv.DictReader(file)
    
        # Counts
        counts = {}
    
        # Iterate over CSV file, counting favorites
        for row in reader:
            favorite = row["language"]
            if favorite in counts:
                counts[favorite] += 1
            else:
                counts[favorite] = 1
    
    # Print counts for favorite in counts:
        print(f"{favorite}: {counts[favorite]}") 
    

    注意到当counts中存在favorite键时,其值会增加。如果它不存在,我们定义counts[favorite]并将其设置为 1。此外,格式化字符串已经得到改进,以展示counts[favorite]

  • Python 也允许对counts进行排序。按照以下方式改进你的代码:

    # Sorts favorites by key 
    import csv
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create DictReader
        reader = csv.DictReader(file)
    
        # Counts
        counts = {}
    
        # Iterate over CSV file, counting favorites
        for row in reader:
            favorite = row["language"]
            if favorite in counts:
                counts[favorite] += 1
            else:
                counts[favorite] = 1
    
    # Print counts for favorite in sorted(counts):
        print(f"{favorite}: {counts[favorite]}") 
    

    注意代码底部的sorted(counts)

  • 如果你查看 Python 文档中sorted函数的参数,你会发现它有许多内置参数。你可以利用一些这些内置参数,如下所示:

    # Sorts favorites by value using .get 
    import csv
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create DictReader
        reader = csv.DictReader(file)
    
        # Counts
        counts = {}
    
        # Iterate over CSV file, counting favorites
        for row in reader:
            favorite = row["language"]
            if favorite in counts:
                counts[favorite] += 1
            else:
                counts[favorite] = 1
    
    # Print counts for favorite in sorted(counts, key=counts.get, reverse=True):
        print(f"{favorite}: {counts[favorite]}") 
    

    注意传递给sorted的参数。key参数允许你告诉 Python 你希望用于排序项的方法。在这种情况下,使用counts.get按值排序。reverse=True告诉sorted从大到小排序。

  • Python 有许多库,我们可以在代码中利用这些库。其中之一是collections,我们可以从中导入CounterCounter将允许你访问每种语言的计数,而无需像我们之前的代码中看到的那样处理所有if语句。你可以按照以下方式实现:

    # Sorts favorites by value using .get 
    import csv
    
    from collections import Counter
    
    # Open CSV file with open("favorites.csv", "r") as file:
    
        # Create DictReader
        reader = csv.DictReader(file)
    
        # Counts
        counts = Counter()
    
        # Iterate over CSV file, counting favorites
        for row in reader:
            favorite = row["language"]
            counts[favorite] += 1
    
    # Print counts for favorite, count in counts.most_common():
        print(f"{favorite}: {count}") 
    

    注意到counts = Counter()如何启用从collections导入的Counter类。

  • 你可以在Python 文档中了解更多关于sorted的信息。

关系型数据库

  • Google、X 和 Meta 都使用关系型数据库来大规模存储他们的信息。

  • 关系型数据库在称为 表格 的结构中以行和列的形式存储数据。

  • SQL 允许四种类型的命令:

     Create
      Read
      Update
      Delete 
    
  • 这四个操作被亲切地称为 CRUD

  • 我们可以使用 SQL 语法CREATE TABLE table (column type, ...);创建一个数据库。但这个命令在哪里运行呢?

  • sqlite3是一种具有本课程所需核心功能的关系型数据库。

  • 我们可以在终端通过输入sqlite3 favorites.db创建一个 SQL 数据库。在被提示时,我们将通过按y键同意创建favorites.db

  • 你会注意到提示符发生了变化,因为我们现在正在使用一个名为sqlite的程序。

  • 我们可以通过输入.mode csvsqlite置于csv模式。然后,我们可以通过输入.import favorites.csv favorites从我们的csv文件导入数据。看起来好像什么都没发生!

  • 我们可以输入.schema来查看数据库的结构。

  • 你可以使用SELECT columns FROM table语法从表中读取项目。

  • 例如,您可以输入SELECT * FROM favorites;,这将打印favorites中的每一行。

  • 您可以使用命令SELECT language FROM favorites;来获取数据子集。

  • SQL 支持许多用于访问数据的命令,包括:

     AVG
      COUNT
      DISTINCT
      LOWER
      MAX
      MIN
      UPPER 
    
  • 例如,您可以输入SELECT COUNT(*) FROM favorites;。此外,您可以输入SELECT DISTINCT language FROM favorites;以获取数据库中个别语言的列表。您甚至可以输入SELECT COUNT(DISTINCT language) FROM favorites;以获取这些语言的计数。

  • SQL 还提供了我们可以在查询中使用的附加命令:

     WHERE       -- adding a Boolean expression to filter our data
      LIKE        -- filtering responses more loosely
      ORDER BY    -- ordering responses
      LIMIT       -- limiting the number of responses
      GROUP BY    -- grouping responses together 
    

    注意我们在 SQL 中使用--来写注释。

SELECT

  • 例如,我们可以执行SELECT COUNT(*) FROM favorites WHERE language = 'C';。将显示计数。

  • 此外,我们可以输入SELECT COUNT(*) FROM favorites WHERE language = 'C' AND problem = 'Hello, World';。注意AND是如何用于缩小我们的结果的。

  • 同样,我们可以执行SELECT language, COUNT(*) FROM favorites GROUP BY language;。这将提供一个临时表,显示语言和计数。

  • 我们可以通过输入以下命令来改进这一点:SELECT language, COUNT(*) FROM favorites GROUP BY language ORDER BY COUNT(*);。这将按count对结果表进行排序。

  • 同样,我们可以执行SELECT COUNT(*) FROM favorites WHERE language = 'C' AND (problem = 'Hello, World' OR problem = 'Hello, It''s Me');。请注意,有两个''标记,以便以不混淆 SQL 的方式使用单引号。

  • 此外,我们可以执行SELECT COUNT(*) FROM favorites WHERE language = 'C' AND problem LIKE 'Hello, %';以找到以Hello,开头的任何问题(包括空格)。

  • 我们还可以通过执行SELECT language, COUNT(*) FROM favorites GROUP BY language;来按每种语言的值进行分组。

  • 我们可以按以下方式排序输出:SELECT language, COUNT(*) FROM favorites GROUP BY language ORDER BY COUNT(*) DESC;

  • 我们甚至可以在查询中创建别名,就像变量一样:SELECT language, COUNT(*) AS n FROM favorites GROUP BY language ORDER BY n DESC;

  • 最后,我们可以限制输出为 1 个或多个值:SELECT language, COUNT(*) AS n FROM favorites GROUP BY language ORDER BY n DESC LIMIT 1;

INSERT

  • 我们还可以使用INSERT INTO table (column...) VALUES(value, ...);的形式将数据INSERT到 SQL 数据库中。

  • 我们可以执行INSERT INTO favorites (language, problem) VALUES ('SQL', 'Fiftyville');

  • 您可以通过执行SELECT * FROM favorites;来验证此收藏夹的增加。

DELETE

  • DELETE允许您删除数据的一部分。例如,您可以DELETE FROM favorites WHERE Timestamp IS NULL;。这将删除任何TimestampNULL的记录。

UPDATE

  • 我们还可以使用UPDATE命令来更新数据。

  • 例如,您可以执行UPDATE favorites SET language = 'SQL', problem = 'Fiftyville';。这将覆盖所有之前将 C 和 Scratch 作为首选编程语言的语句。

  • 注意,这些查询具有巨大的威力。因此,在实际环境中,您应该考虑谁有权限执行某些命令,以及您是否有可用的备份!

IMDb

  • 我们可以想象一个我们可能想要创建的数据库,用于分类各种电视节目。我们可以创建一个包含诸如titlestarstarstarstar以及更多星星的电子表格。这种方法的缺点是它有很多浪费的空间。有些节目可能只有一个明星。而有些节目可能有几十个。

  • 我们可以将数据库分成多个工作表。我们可以有一个shows工作表、一个stars工作表和一个people工作表。在people工作表中,每个人可以有一个唯一的id。在shows工作表中,每个节目也可以有一个唯一的id。在名为stars的第三个工作表中,我们可以通过拥有show_idperson_id来关联每个节目对应的人员。虽然这是一个改进,但这并不是一个理想的数据库。

  • IMDb 提供了人员、节目、编剧、明星、类型和评分的数据库。这些表彼此之间如下相关:

    代表各种 SQL 表的六个盒子,箭头指向每个盒子,显示它们彼此之间的多对多关系

  • 下载完shows.db后,你可以在终端窗口中执行sqlite3 shows.db

  • 让我们聚焦于数据库中名为showsratings的两个表之间的关系。这两个表之间的关系可以如下表示:

    一个称为 shows 的盒子和一个称为 ratings 的盒子

  • 为了说明这些表之间的关系,我们可以执行以下命令:SELECT * FROM ratings LIMIT 10;。检查输出后,我们可以执行SELECT * FROM shows LIMIT 10;

  • 检查showsrating,我们可以看到它们之间存在一对一的关系:一个节目有一个评分。

  • 要了解数据库,在执行.schema后,您不仅会发现每个表,还会发现每个字段中的各个字段。

  • 更具体地说,您可以通过执行.schema shows来了解shows内部的字段。您也可以执行.schema ratings来查看ratings内部的字段。

  • 如您所见,show_id存在于所有表中。在shows表中,它简单地被称为id。这个在所有字段之间都存在的公共字段被称为。主键用于在表中标识唯一记录。外键用于通过指向另一个表中的主键来建立表之间的关系。您可以在ratings模式的方案中看到show_id是一个外键,它引用了shows中的id

  • 通过将数据存储在上述关系型数据库中,数据可以更有效地存储。

  • sqlite 中,我们有五种数据类型,包括:

     BLOB       -- binary large objects that are groups of ones and zeros
      INTEGER    -- an integer
      NUMERIC    -- for numbers that are formatted specially like dates
      REAL       -- like a float
      TEXT       -- for strings and the like 
    
  • 此外,可以将列设置为添加特殊约束:

     NOT NULL
      UNIQUE 
    
  • 我们可以进一步利用这些数据来理解这些关系。执行SELECT * FROM ratings;。这里有很多评分!

  • 我们可以通过执行SELECT show_id FROM ratings WHERE rating >= 6.0 LIMIT 10;进一步限制这些数据。从这个查询中,你可以看到有 10 个节目被展示。然而,我们不知道每个show_id代表什么节目。

  • 你可以通过执行SELECT * FROM shows WHERE id = 626124;来发现这些节目是什么。

  • 通过执行以下命令,我们可以使查询更高效:

    SELECT title
    FROM shows
    WHERE id IN (
        SELECT show_id
        FROM ratings
        WHERE rating >= 6.0
        LIMIT 10
    ) 
    

    注意,这个查询嵌套了两个查询。内部查询被外部查询使用。

JOINs

  • 我们正在从showsratings表中获取数据。注意showsratings都有一个共同的id

  • 我们如何临时合并表?可以使用JOIN命令将表连接在一起。

  • 执行以下命令:

    SELECT * FROM shows
      JOIN ratings on shows.id = ratings.show_id
      WHERE rating >= 6.0
      LIMIT 10; 
    

    注意,这会产生一个比我们之前看到的更宽的表。

  • 在之前的查询中已经展示了这些键之间的一对一关系,让我们来检查一些一对多关系。关注genres表,执行以下命令:

    SELECT * FROM genres
    LIMIT 10; 
    

    注意,这为我们提供了对原始数据的感觉。你可能注意到一个节目有三个值。这是一个一对多关系。

  • 通过输入.schema genres,我们可以了解更多关于genres表的信息。

  • 执行以下命令来了解更多关于数据库中各种喜剧的信息:

    SELECT title FROM shows
    WHERE id IN (
      SELECT show_id FROM genres
      WHERE genre = 'Comedy'
      LIMIT 10
    ); 
    

    注意,这产生了一个包括猫怪在内的喜剧列表。

  • 要了解更多关于 Catweazle 的信息,可以通过连接各种表来实现:

    SELECT * FROM shows
    JOIN genres
    ON shows.id = genres.show_id
    WHERE id = 63881; 
    

    注意,这会产生一个临时表。拥有一个重复的表是可以的。

  • 与一对一和一对多关系相比,可能存在多对多关系。

  • 通过执行以下命令,我们可以了解更多关于节目办公室及其演员的信息:

    SELECT name FROM people WHERE id IN 
        (SELECT person_id FROM stars WHERE show_id = 
            (SELECT id FROM shows WHERE title = 'The Office' AND year = 2005)); 
    

    注意,这会产生一个包含通过嵌套查询的各种明星名字的表。

  • 我们找到史蒂夫·卡瑞尔主演的所有节目:

    SELECT title FROM shows WHERE id IN 
        (SELECT show_id FROM stars WHERE person_id = 
            (SELECT id FROM people WHERE name = 'Steve Carell')); 
    

    这会产生一个史蒂夫·卡瑞尔主演的节目标题列表。

  • 这也可以用这种方式表达:

    SELECT title FROM shows, stars, people 
    WHERE shows.id = stars.show_id
    AND people.id = stars.person_id
    AND name = 'Steve Carell'; 
    
  • 可以使用通配符%运算符来查找所有名字以Steve C开头的人,可以使用以下语法:SELECT * FROM people WHERE name LIKE 'Steve C%';

索引

  • 虽然关系数据库比使用CSV文件具有更快和更健壮的能力,但可以使用索引在表中优化数据。

  • 索引可以被用来加速我们的查询。

  • 我们可以通过在sqlite3中执行.timer on来跟踪查询的速度。

  • 要了解索引如何加速查询,运行以下命令:SELECT * FROM shows WHERE title = 'The Office';注意查询执行后显示的时间。

  • 然后,我们可以使用以下语法创建索引:CREATE INDEX title_index ON shows (title);。这告诉sqlite3创建一个索引并对此列title进行一些特殊的底层优化。

  • 这将创建一个名为B 树的数据结构,其外观类似于二叉树。然而,与二叉树不同,可以有超过两个子节点。

    从顶部有一个节点,该节点有四个子节点,下面有三个子节点来自一个节点,两个来自另一个节点,另外两个来自另一个节点,三个来自另一个节点

  • 此外,我们可以创建索引如下:

    CREATE INDEX name_index ON people (name);
    CREATE INDEX person_index ON stars (person_id); 
    
  • 运行查询后,你会注意到查询运行得更快!

    SELECT title FROM shows WHERE id IN 
        (SELECT show_id FROM stars WHERE person_id = 
            (SELECT id FROM people WHERE name = 'Steve Carell')); 
    
  • 不幸的是,索引所有列将导致使用更多的存储空间。因此,在提高速度和存储空间之间有一个权衡。

在 Python 中使用 SQL

  • 为了帮助在这个课程中处理 SQL,可以在你的代码中使用 CS50 库如下:

    from cs50 import SQL 
    
  • 与之前对 CS50 库的使用类似,这个库将帮助你在 Python 代码中利用 SQL 的复杂步骤。

  • 你可以在文档中了解更多关于 CS50 库的 SQL 功能。

  • 使用我们对 SQL 的新知识,我们现在可以利用 Python。

  • 按照以下方式修改你的favorites.py代码:

    # Searches database popularity of a problem 
    from cs50 import SQL
    
    # Open database db = SQL("sqlite:///favorites.db")
    
    # Prompt user for favorite favorite = input("Favorite: ")
    
    # Search for title rows = db.execute("SELECT COUNT(*) AS n FROM favorites WHERE language = ?", favorite)
    
    # Get first (and only) row row = rows[0]
    
    # Print popularity print(row["n"]) 
    

    注意,db = SQL("sqlite:///favorites.db")为 Python 提供了数据库文件的位置。然后,以rows开头的行执行使用db.execute的 SQL 命令。确实,这个命令将引号内的语法传递给db.execute函数。我们可以使用这种语法发出任何 SQL 命令。此外,注意rows作为字典列表返回。在这种情况下,只有一个结果,一行,作为字典返回到rows列表中。

竞争条件

  • 有时使用 SQL 可能会导致一些问题。

  • 你可以想象一个场景,多个用户可能同时访问同一个数据库并执行命令。

  • 这可能导致代码被其他人的行为中断,从而造成数据丢失。

  • 内置的 SQL 功能如BEGIN TRANSACTIONCOMMITROLLBACK有助于避免一些这些竞争条件问题。

SQL 注入攻击

  • 现在,仍然考虑上面的代码,你可能想知道上面的?问号的作用。在 SQL 的现实中应用中可能出现的一个问题是所谓的注入攻击。注入攻击是指恶意行为者可以输入恶意的 SQL 代码。

  • 例如,考虑以下登录界面:

    哈佛密钥登录界面,包含用户名和密码字段

  • 如果在我们的代码中没有适当的安全措施,恶意行为者可以运行恶意代码。考虑以下:

    rows = db.execute("SELECT COUNT(*) FROM users WHERE username = ? AND password = ?", username, password) 
    

    注意,因为?符号的位置,在查询盲目接受之前,可以在favorite上运行验证。

  • 你永远不希望在查询中使用上述格式化的字符串或盲目信任用户的输入。

  • 利用 CS50 库,该库将净化并移除任何潜在的恶意字符。

总结

在本节课中,你学习了更多与 Python 相关的语法。此外,你学习了如何将这一知识整合到以平面文件和关系数据库形式存在的数据中。最后,你了解了SQL。具体来说,我们讨论了…

  • 平面文件数据库

  • 关系数据库

  • SELECTCREATEINSERTDELETEUPDATE 等 SQL 命令。

  • 主键和外键

  • JOINs

  • 索引

  • 在 Python 中使用 SQL

  • 竞态条件

  • SQL 注入攻击

次次见!

第八讲

原文:cs50.harvard.edu/x/notes/8/

  • 欢迎光临!

  • 互联网

  • 路由器

  • DNS

  • DHCP

  • HTTPS

  • HTML

  • 正则表达式

  • CSS

  • 框架

  • JavaScript

  • 总结

欢迎光临!

  • 在前几周,我们向您介绍了 Python,这是一种高级编程语言,它使用了我们在 C 语言中学到的相同构建块。今天,我们将进一步扩展这些构建块,在 HTML、CSS 和 JavaScript 中。

互联网

  • 互联网是我们所有人都使用的技术。

  • 使用我们前几周学到的技能,我们可以构建自己的网页和应用。

  • ARPANET 将互联网上的第一个节点连接在一起。

  • 两点之间的点可以被认为是 路由器

路由器

  • 为了将数据从一个地方路由到另一个地方,我们需要做出 路由决策。也就是说,有人需要编程数据如何从 A 点传输到 B 点。

  • 你可以想象数据可以从 A 点到 B 点有多个路径,当路由器拥堵时,数据可以通过另一条路径流动。数据 数据包 从一个路由器传输到另一个路由器,从一个计算机传输到另一个计算机。

  • TCP/IP 是两种协议,允许计算机在互联网上相互传输数据。

  • IP互联网协议 是一种计算机可以在互联网上相互识别的方式。每台计算机在世界上都有一个唯一的地址。地址的形式如下:

    #.#.#.# 
    
  • 数字范围从 0255。IP 地址是 32 位,这意味着这些地址可以容纳超过 40 亿个地址。较新的 IP 地址版本,采用 128 位,可以容纳更多的计算机!

  • 在现实世界中,服务器为我们做了很多工作。

  • 数据包的结构如下:

    0                   1                   2                   3  
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |Version|  IHL  |Type of Service|          Total Length         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |         Identification        |Flags|      Fragment Offset    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |  Time to Live |    Protocol   |         Header Checksum       |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                       Source Address                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                    Destination Address                        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                    Options                    |    Padding    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
    
  • 数据包是标准化的。源地址和目标地址包含在每个数据包中。

  • TCP,或传输控制协议,有助于跟踪发送的数据包顺序。

  • 此外,TCP 用于区分不同的网络服务。例如,80 用于表示 HTTP,443 用于表示 HTTPS。这些数字是 端口号

  • 当信息从一个位置发送到另一个位置时,会发送源 IP 地址、目标 IP 地址和 TCP 端口号。

  • 这些协议也被用来将大文件分成多个部分或数据包。例如,一张大猫的照片可以分成多个数据包发送。当一个数据包丢失时,TCP/IP 可以从原始服务器再次请求丢失的数据包。

  • TCP 将在所有数据都已传输和接收后进行确认。

DNS

  • 如果你需要记住一个 IP 地址来访问一个网站,那将会非常麻烦。

  • DNS,或 域名系统,是互联网上一组服务器,用于将像 harvard.edu 这样的网站地址路由到特定的 IP 地址。

  • DNS 简单来说是一个将特定的、完全限定的域名与特定的 IP 地址链接起来的表或数据库。

DHCP

  • DHCP 是一个确定你的设备 IP 地址的协议。

  • 此外,此协议定义了你的设备使用的默认网关和名称服务器。

HTTPS

  • HTTP超文本传输协议 是开发者用来通过数据从一个地方传输到另一个地方来构建强大和有用事物的应用层协议。HTTPS 是此协议的安全版本。

  • 当你看到一个地址如 https://www.example.com 时,你实际上是在隐式地访问该地址,并在其末尾有一个 /

  • 路径 是在斜杠之后存在的部分。例如,https://www.example.com/folder/file.html 访问 example.com 并浏览到 folder 目录,然后访问名为 file.html 的文件。

  • .com 被称为 顶级域名,用于表示与该地址关联的位置或组织类型。

  • 在此地址中的 https 是用来连接该网页地址的协议。通过协议,我们指的是 HTTP 使用 GETPOST 请求 从服务器获取信息。例如,你可以启动 Google Chrome,右键点击,并点击 inspect。当你打开 开发者工具 并访问 Network,选择 Preserve log,你会看到 Request Headers。你会看到 GET 的提及。这在其他浏览器中也是可能的,使用稍微不同的方法。

  • 例如,在发出 GET 请求时,你的电脑可能向服务器发送以下内容:

    GET / HTTP/2
    Host: www.harvard.edu 
    

    注意,这是通过 HTTP 请求在 www.harvard.edu 上提供的内容。

  • 通常,在向服务器发出请求后,你将在 Response Headers 中收到以下内容:

    HTTP/2 200
    Content-Type: text/html 
    
  • 检查这些日志的方法可能比必要的要复杂一些。你可以在 cs50.dev 上分析 HTTP 协议的工作。例如,在你的终端窗口中输入以下内容:

    curl -I https://www.harvard.edu/ 
    

    注意,此命令的输出返回了服务器响应的所有头部值。

  • 通过你的网页浏览器的开发者工具,你可以看到浏览上述网站时所有的 HTTP 请求。

  • 此外,在你的终端窗口中执行以下命令:

    curl -I https://harvard.edu 
    

    注意,你会看到一个 301 响应,为浏览器提供了一个指向正确网站的提示。

  • 类似地,在你的终端窗口中执行以下命令:

    curl -I http://www.harvard.edu/ 
    

    注意,https 中的 s 已被移除。服务器响应将显示响应为 301,这意味着网站已永久迁移。

  • 301 类似,404 状态码意味着指定的 URL 未找到。还有许多其他的响应代码,例如:

    200 OK
    301 Moved Permanently
    302 Found
    304 Not Modified
    307 Temporary Redirect
    401 Unauthorized
    403 Forbidden
    404 Not Found
    418 I'm a Teapot
    500 Internal Server Error
    503 Service Unavailable 
    
  • 值得注意的是,当 500 错误涉及到你创建的产品或应用程序时,这总是作为开发者的你的责任。这将在下周的问题集中尤为重要,也许对你的最终项目也是如此!

HTML

  • HTML超文本标记语言标签 组成,每个标签可能有一些 属性 来描述它。

  • 在你的终端中,输入 code hello.html 并编写如下代码:

    <!DOCTYPE html>
    
    <!-- Demonstrates HTML -->
    
    <html lang="en">
        <head>
            <title>hello, title</title>
        </head>
        <body>
            hello, body
        </body>
    </html> 
    

    注意到 html 标签既打开了又关闭了这个文件。此外,注意 lang 属性,它修改了 html 标签的行为。还要注意,既有 head 标签也有 body 标签。缩进不是必需的,但确实暗示了一个层次结构。

  • 你可以通过输入 http-server 来提供你的代码。现在,提供的内容可以通过一个非常长的 URL 访问。如果你点击它,你可以访问由你自己的代码生成的网站。

  • 当你访问这个 URL 时,注意文件名 hello.html 出现在这个 URL 的末尾。此外,根据 URL,注意服务器是通过端口 8080 提供服务的。

  • 标签的层次结构可以表示如下:

    html 代码旁边显示层次结构,显示父节点和子节点

  • 了解这个层次结构将在我们学习 JavaScript 时非常有用。

  • 浏览器将自上而下、从左到右读取你的 HTML 文件。

  • 由于在 HTML 中空白和缩进实际上被忽略,你需要使用 <p> 段落标签来打开和关闭一个段落。考虑以下:

    <!DOCTYPE html>
    
    <!-- Demonstrates paragraphs -->
    
    <html lang="en">
        <head>
            <title>paragraphs</title>
        </head>
        <body>
            <p>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus convallis scelerisque quam, vel hendrerit lectus viverra eu. Praesent posuere eget lectus ut faucibus. Etiam eu velit laoreet, gravida lorem in, viverra est. Cras ut purus neque. In porttitor non lorem id lobortis. Mauris gravida metus libero, quis maximus dui porta at. Donec lacinia felis consectetur venenatis scelerisque. Nulla eu nisl sollicitudin, varius velit sit amet, vehicula erat. Curabitur sollicitudin felis sit amet orci mattis, a tempus nulla pulvinar. Aliquam erat volutpat.
            </p>
            <p>
                Mauris ut dui in eros semper hendrerit. Morbi vel elit mi. Sed sit amet ex non quam dignissim dignissim et vel arcu. Pellentesque eget elementum orci. Morbi ac cursus ex. Pellentesque quis turpis blandit orci dapibus semper sed non nunc. Nulla et dolor nec lacus finibus volutpat. Sed non lorem diam. Donec feugiat interdum interdum. Vivamus et justo in enim blandit fermentum vel at elit. Phasellus eu ante vitae ligula varius aliquet. Etiam id posuere nibh.
            </p>
            <p>
                Aenean venenatis convallis ante a rhoncus. Nullam in metus vel diam vehicula tincidunt. Donec lacinia metus sem, sit amet egestas elit blandit sit amet. Nunc egestas sem quis nisl mattis semper. Pellentesque ut magna congue lorem eleifend sodales. Donec tortor tortor, aliquam vitae mollis sed, interdum ut lectus. Mauris non purus quis ipsum lacinia tincidunt.
            </p>
            <p>
                Integer at justo lacinia libero blandit aliquam ut ut dui. Quisque tincidunt facilisis venenatis. Nullam dictum odio quis lorem luctus, vel malesuada dolor luctus. Aenean placerat faucibus enim a facilisis. Maecenas eleifend quis massa sed eleifend. Ut ultricies, dui ac vulputate hendrerit, ex metus iaculis diam, vitae fermentum libero dui et ante. Phasellus suscipit, arcu ut consequat sagittis, massa urna accumsan massa, eu aliquet nulla lorem vitae arcu. Pellentesque rutrum felis et metus porta semper. Nam ac consectetur mauris.
            </p>
            <p>
                Suspendisse rutrum vestibulum odio, sed venenatis purus condimentum sed. Morbi ornare tincidunt augue eu auctor. Vivamus sagittis ac lectus at aliquet. Nulla urna mauris, interdum non nibh in, vehicula porta enim. Donec et posuere sapien. Pellentesque ultrices scelerisque ipsum, vel fermentum nibh tincidunt et. Proin gravida porta ipsum nec scelerisque. Vestibulum fringilla erat at turpis laoreet, nec hendrerit nisi scelerisque.
            </p>
            <p>
                Sed quis malesuada mi. Nam id purus quis augue sagittis pharetra. Nulla facilisi. Maecenas vel fringilla ante. Cras tristique, arcu sit amet blandit auctor, urna elit ultricies lacus, a malesuada eros dui id massa. Aliquam sem odio, pretium vel cursus eget, scelerisque at urna. Vestibulum posuere a turpis consectetur consectetur. Cras consequat, risus quis tempor egestas, nulla ipsum ornare erat, nec accumsan nibh lorem nec risus. Integer at iaculis lacus. Integer congue nunc massa, quis molestie felis pellentesque vestibulum. Nulla odio tortor, aliquam nec quam in, ornare aliquet sapien.
            </p>
        </body>
    </html> 
    

    注意到段落从 <p> 标签开始,并以 </p> 标签结束。

  • HTML 允许表示标题:

    <!DOCTYPE html>
    
    <!-- Demonstrates headings (for chapters, sections, subsections, etc.) -->
    
    <html lang="en">
    
        <head>
            <title>headings</title>
        </head>
    
        <body>
    
            <h1>One</h1>
            <p>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus convallis scelerisque quam, vel hendrerit lectus viverra eu. Praesent posuere eget lectus ut faucibus. Etiam eu velit laoreet, gravida lorem in, viverra est. Cras ut purus neque. In porttitor non lorem id lobortis. Mauris gravida metus libero, quis maximus dui porta at. Donec lacinia felis consectetur venenatis scelerisque. Nulla eu nisl sollicitudin, varius velit sit amet, vehicula erat. Curabitur sollicitudin felis sit amet orci mattis, a tempus nulla pulvinar. Aliquam erat volutpat.
            </p>
    
            <h2>Two</h2>
            <p>
                Mauris ut dui in eros semper hendrerit. Morbi vel elit mi. Sed sit amet ex non quam dignissim dignissim et vel arcu. Pellentesque eget elementum orci. Morbi ac cursus ex. Pellentesque quis turpis blandit orci dapibus semper sed non nunc. Nulla et dolor nec lacus finibus volutpat. Sed non lorem diam. Donec feugiat interdum interdum. Vivamus et justo in enim blandit fermentum vel at elit. Phasellus eu ante vitae ligula varius aliquet. Etiam id posuere nibh.
            </p>
    
            <h3>Three</h3>
            <p>
                Aenean venenatis convallis ante a rhoncus. Nullam in metus vel diam vehicula tincidunt. Donec lacinia metus sem, sit amet egestas elit blandit sit amet. Nunc egestas sem quis nisl mattis semper. Pellentesque ut magna congue lorem eleifend sodales. Donec tortor tortor, aliquam vitae mollis sed, interdum ut lectus. Mauris non purus quis ipsum lacinia tincidunt.
            </p>
    
            <h4>Four</h4>
            <p>
                Integer at justo lacinia libero blandit aliquam ut ut dui. Quisque tincidunt facilisis venenatis. Nullam dictum odio quis lorem luctus, vel malesuada dolor luctus. Aenean placerat faucibus enim a facilisis. Maecenas eleifend quis massa sed eleifend. Ut ultricies, dui ac vulputate hendrerit, ex metus iaculis diam, vitae fermentum libero dui et ante. Phasellus suscipit, arcu ut consequat sagittis, massa urna accumsan massa, eu aliquet nulla lorem vitae arcu. Pellentesque rutrum felis et metus porta semper. Nam ac consectetur mauris.
            </p>
    
            <h5>Five</h5>
            <p>
                Suspendisse rutrum vestibulum odio, sed venenatis purus condimentum sed. Morbi ornare tincidunt augue eu auctor. Vivamus sagittis ac lectus at aliquet. Nulla urna mauris, interdum non nibh in, vehicula porta enim. Donec et posuere sapien. Pellentesque ultrices scelerisque ipsum, vel fermentum nibh tincidunt et. Proin gravida porta ipsum nec scelerisque. Vestibulum fringilla erat at turpis laoreet, nec hendrerit nisi scelerisque.
            </p>
    
            <h6>Six</h6>
            <p>
                Sed quis malesuada mi. Nam id purus quis augue sagittis pharetra. Nulla facilisi. Maecenas vel fringilla ante. Cras tristique, arcu sit amet blandit auctor, urna elit ultricies lacus, a malesuada eros dui id massa. Aliquam sem odio, pretium vel cursus eget, scelerisque at urna. Vestibulum posuere a turpis consectetur consectetur. Cras consequat, risus quis tempor egestas, nulla ipsum ornare erat, nec accumsan nibh lorem nec risus. Integer at iaculis lacus. Integer congue nunc massa, quis molestie felis pellentesque vestibulum. Nulla odio tortor, aliquam nec quam in, ornare aliquet sapien.
            </p>
    
        </body>
    
    </html> 
    

    注意到 <h1><h2><h3> 表示不同的标题级别。

  • 我们也可以在 HTML 中创建无序列表:

    <!DOCTYPE html>
    
    <!-- Demonstrates (ordered) lists -->
    
    <html lang="en">
        <head>
            <title>list</title>
        </head>
        <body>
            <ul>
                <li>foo</li>
                <li>bar</li>
                <li>baz</li>
            </ul>
        </body>
    </html> 
    

    注意到 <ul> 标签创建了一个包含三个项目的无序列表。

  • 我们也可以在 HTML 中创建有序列表:

    <!DOCTYPE html>
    
    <!-- Demonstrates (ordered) lists -->
    
    <html lang="en">
        <head>
            <title>list</title>
        </head>
        <body>
            <ol>
                <li>foo</li>
                <li>bar</li>
                <li>baz</li>
            </ol>
        </body>
    </html> 
    

    注意到 <ol> 标签创建了一个包含三个项目的有序列表。

  • 我们也可以在 HTML 中创建一个表格:

    <!DOCTYPE html>
    
    <!-- Demonstrates table -->
    
    <html lang="en">
        <head>
            <title>table</title>
        </head>
        <body>
            <table>
                <tr>
                    <td>1</td>
                    <td>2</td>
                    <td>3</td>
                </tr>
                <tr>
                    <td>4</td>
                    <td>5</td>
                    <td>6</td>
                </tr>
                <tr>
                    <td>7</td>
                    <td>8</td>
                    <td>9</td>
                </tr>
                <tr>
                    <td>*</td>
                    <td>0</td>
                    <td>#</td>
                </tr>
            </table>
        </body>
    </html> 
    

    表格也有打开和关闭每个元素的标签。此外,注意 HTML 中注释的语法。

  • 图像也可以在 HTML 中使用:

    <!DOCTYPE html>
    
    <!-- Demonstrates image -->
    
    <html lang="en">
        <head>
            <title>image</title>
        </head>
        <body>
            <img alt="photo of bridge" src="bridge.png">
        </body>
    </html> 
    

    注意到 src="bridge.png" 指示了图像文件可以找到的路径。

  • 视频也可以包含在 HTML 中:

    <!DOCTYPE html>
    
    <!-- Demonstrates video -->
    
    <html lang="en">
        <head>
            <title>video</title>
        </head>
        <body>
            <video controls muted>
                <source src="video.mp4" type="video/mp4">
            </video>
        </body>
    </html> 
    

    注意到 type 属性指定这是一个 mp4 类型的视频。此外,注意 controlsmuted 是如何传递给 video 的。

  • 你也可以在各个网页之间建立链接:

    <!DOCTYPE html>
    
    <!-- Demonstrates link -->
    
    <html lang="en">
        <head>
            <title>link</title>
        </head>
        <body>
           Visit <a href="https://www.harvard.edu">Harvard</a>.
        </body>
    </html> 
    

    注意到 <a>锚点 标签用于使 Harvard 成为可链接的文本。

  • 你也可以创建类似于 Google 搜索的表单:

    <!DOCTYPE html>
    
    <!-- Demonstrates form -->
    
    <html lang="en">
        <head>
            <title>search</title>
        </head>
        <body>
            <form action="https://www.google.com/search" method="get">
                <input name="q" type="search">
                <input type="submit" value="Google Search">
            </form>
        </body>
    </html> 
    

    注意到 form 标签打开并提供它将采取的 action 属性。input 字段被包含在内,传递名称 q 和类型为 search

  • 我们可以如下改进这个搜索:

    <!DOCTYPE html>
    
    <!-- Demonstrates additional form attributes -->
    
    <html lang="en">
        <head>
            <title>search</title>
        </head>
        <body>
            <form action="https://www.google.com/search" method="get">
                <input autocomplete="off" autofocus name="q" placeholder="Query" type="search">
                <button>Google Search</button>
            </form>
        </body>
    </html> 
    

    注意到 autocomplete 被设置为 offautofocus 被启用。

  • 我们已经看到了许多你可以添加到网站上的 HTML 元素中的一部分。如果你有关于要添加到网站上的想法(我们还没有看到,比如按钮、音频文件等),尝试在 Google 上搜索“X in HTML”以找到正确的语法!同样,你可以使用 cs50.ai 来帮助你发现更多的 HTML 功能!

正则表达式

  • 正则表达式regexes 是一种确保用户提供的数据符合特定格式的手段。

  • 我们可以自己实现一个利用正则表达式的注册页面,如下所示:

    <!DOCTYPE html>
    
    <!-- Demonstrates type="email" -->
    
    <html lang="en">
        <head>
            <title>register</title>
        </head>
        <body>
            <form>
                <input autocomplete="off" autofocus name="email" placeholder="Email" type="email">
                <button>Register</button>
            </form>
        </body>
    </html> 
    

    注意,input 标签包含属性指定这是 email 类型的。浏览器知道要双重检查输入是否为电子邮件地址。

  • 虽然浏览器使用这些内置属性来检查电子邮件地址,但我们可以添加一个 pattern 属性来确保只有特定的数据出现在电子邮件地址中:

    <!DOCTYPE html>
    
    <!-- Demonstrates pattern attribute -->
    
    <html lang="en">
        <head>
            <title>register</title>
        </head>
        <body>
            <form>
                <input autocomplete="off" autofocus name="email" pattern=".+@.+\.edu" placeholder="Email" type="email">
                <button>Register</button>
            </form>
        </body>
    </html> 
    

    注意,pattern 属性被传递了一个正则表达式,表示电子邮件地址必须包含一个 @ 符号和一个 .edu

  • 您可以从 Mozilla 的文档 中了解更多关于正则表达式的信息。此外,您可以访问 cs50.ai 获取提示。

CSS

  • CSS,或 层叠样式表,是一种标记语言,允许您微调 HTML 文件的审美。

  • CSS 中充满了 属性,它们包括键值对。

  • 在您的终端中,键入 code home.html 并编写如下代码:

    <!DOCTYPE html>
    
    <!-- Demonstrates inline CSS with P tags -->
    
    <html lang="en">
        <head>
            <title>css</title>
        </head>
        <body>
            <p style="font-size: large; text-align: center;">
                John Harvard
            </p>
            <p style="font-size: medium; text-align: center;">
                Welcome to my home page!
            </p>
            <p style="font-size: small; text-align: center;">
                Copyright &#169; John Harvard
            </p>
        </body>
    </html> 
    

    注意,一些 style 属性被提供给 <p> 标签。font-size 被设置为 largemediumsmall。然后 text-align 被设置为居中。

  • 虽然正确,但上述设计并不理想。我们可以通过修改代码来去除冗余,如下所示:

    <!DOCTYPE html>
    
    <!-- Removes outer DIV -->
    
    <html lang="en">
        <head>
            <title>css</title>
        </head>
        <body style="text-align: center">
            <div style="font-size: large">
                John Harvard
            </div>
            <div style="font-size: medium">
                Welcome to my home page!
            </div>
            <div style="font-size: small">
                Copyright &#169; John Harvard
            </div>
        </body>
    </html> 
    

    注意,<div> 标签被用来将这个 HTML 文件划分为特定的区域。text-align: center 被应用于整个 HTML 的主体部分。因为 body 内部的所有内容都是 body 的子元素,所以 center 属性会级联到这些子元素。

  • 结果表明,HTML 中包含了一些新的语义标签。我们可以如下修改我们的代码:

    <!DOCTYPE html>
    
    <!-- Uses semantic tags instead of DIVs -->
    
    <html lang="en">
        <head>
            <title>css</title>
        </head>
        <body style="text-align: center">
            <header style="font-size: large">
                John Harvard
            </header>
            <main style="font-size: medium">
                Welcome to my home page!
            </main>
            <footer style="font-size: small">
                Copyright &#169; John Harvard
            </footer>
        </body>
    </html> 
    

    注意,headerfooter 都被分配了不同的样式。

  • 将样式和信息都放在同一个位置的做法并不好。我们可以将样式元素移动到文件顶部,如下所示:

    <!-- Demonstrates class selectors -->
    
    <html lang="en">
        <head>
            <style>
    
                .centered
                {
                    text-align: center;
                }
    
                .large
                {
                    font-size: large;
                }
    
                .medium
                {
                    font-size: medium;
                }
    
                .small
                {
                    font-size: small;
                }
    
            </style>
            <title>css</title>
        </head>
        <body class="centered">
            <header class="large">
                John Harvard
            </header>
            <main class="medium">
                Welcome to my home page!
            </main>
            <footer class="small">
                Copyright &#169; John Harvard
            </footer>
        </body>
    </html> 
    

    注意,所有的样式标签都被放置在 head 部分的 style 标签包装器中。此外,注意我们已经为我们的元素分配了名为 centeredlargemediumsmall,并且我们通过在名称前放置一个点来选择这些类,例如 .centered

  • 结果表明,我们可以将所有的样式代码移动到一个特殊的文件中,称为 CSS 文件。我们可以创建一个名为 style.css 的文件,并将我们的类粘贴在那里:

    .centered
    {
        text-align: center;
    }
    
    .large
    {
        font-size: large;
    }
    
    .medium
    {
        font-size: medium;
    }
    
    .small
    {
        font-size: small;
    } 
    

    注意,这正是出现在我们的 HTML 文件中的内容。

  • 然后,我们可以告诉浏览器在哪里找到这个 HTML 文件的 CSS:

    <!DOCTYPE html>
    
    <!-- Demonstrates external stylesheets -->
    
    <html lang="en">
        <head>
            <link href="style.css" rel="stylesheet">
            <title>css</title>
        </head>
        <body class="centered">
            <header class="large">
                John Harvard
            </header>
            <main class="medium">
                Welcome to my home page!
            </main>
            <footer class="small">
                Copyright &#169; John Harvard
            </footer>
        </body>
    </html> 
    

    注意,style.css 被链接到这个 HTML 文件作为样式表,告诉浏览器在哪里找到我们创建的样式。

框架

  • 与我们可以在 Python 中利用的第三方库类似,还有称为 框架 的第三方库,我们可以利用这些框架与我们的 HTML 文件一起使用。

  • Bootstrap是我们可以使用来美化我们的 HTML 并轻松完善设计元素的框架之一,这样我们的页面就更容易阅读。

  • 通过在 HTML 文件的head部分添加以下link标签,可以使用 Bootstrap:

    <head>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
        <title>bootstrap</title>
    </head> 
    
  • 考虑以下 HTML 代码:

    <!DOCTYPE html>
    
    <!-- Demonstrates table -->
    
    <html lang="en">
        <head>
            <title>phonebook</title>
        </head>
        <body>
            <table>
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Number</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>Carter</td>
                        <td>+1-617-495-1000</td>
                    </tr>
                    <tr>
                        <td>David</td>
                        <td>+1-617-495-1000</td>
                    </tr>
                    <tr>
                        <td>John</td>
                        <td>+1-949-468-2750</td>
                    </tr>
                </tbody>
            </table>
        </body>
    </html> 
    

    注意,当查看这个页面的服务版本时,它相当简单。

  • 现在考虑以下实现 Bootstrap 使用的 HTML 代码:

    <!DOCTYPE html>
    
    <!-- Demonstrates table with Bootstrap -->
    
    <html lang="en">
        <head>
            <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
            <title>phonebook</title>
        </head>
        <body>
            <table class="table">
                <thead>
                    <tr>
                        <th scope="col">Name</th>
                        <th scope="col">Number</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>Carter</td>
                        <td>+1-617-495-1000</td>
                    </tr>
                    <tr>
                        <td>David</td>
                        <td>+1-949-468-2750</td>
                    </tr>
                </tbody>
            </table>
        </body>
    </html> 
    

    注意,现在这个网站看起来多么漂亮。

  • 类似地,考虑以下我们之前创建的搜索页面的扩展:

    <!DOCTYPE html>
    
    <!-- Demonstrates layout with Bootstrap -->
    
    <html lang="en">
        <head>
            <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
            <title>search</title>
        </head>
        <body>
    
            <div class="container-fluid">
    
                <ul class="m-3 nav">
                    <li class="nav-item">
                        <a class="nav-link text-dark" href="https://about.google/">About</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link text-dark" href="https://store.google.com/">Store</a>
                    </li>
                    <li class="nav-item ms-auto">
                        <a class="nav-link text-dark" href="https://www.google.com/gmail/">Gmail</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link text-dark" href="https://www.google.com/imghp">Images</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link text-dark" href="https://www.google.com/intl/en/about/products">
                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-grid-3x3-gap-fill" viewBox="0 0 16 16">
                                <path d="M1 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V2zM1 7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V7zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7zM1 12a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2z"/>
                            </svg>
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="btn btn-primary" href="https://accounts.google.com/ServiceLogin" role="button">Sign in</a>
                    </li>
                </ul>
    
                <div class="text-center">
    
                    <!-- https://knowyourmeme.com/memes/happy-cat -->
                    <img alt="Happy Cat" class="img-fluid w-25" src="cat.gif">
    
                    <form action="https://www.google.com/search" class="mt-4" method="get">
                        <input autocomplete="off" autofocus class="form-control form-control-lg mb-4 mx-auto w-50" name="q" placeholder="Query" type="search">
                        <button class="btn btn-light">Google Search</button>
                        <button class="btn btn-light" name="btnI">I'm Feeling Lucky</button>
                    </form>
    
                </div>
    
            </div>
    
        </body>
    </html> 
    

    这个版本的页面非常风格化,多亏了 Bootstrap。

  • 你可以在Bootstrap 文档中了解更多相关信息。

JavaScript

  • JavaScript 是另一种允许在网页中进行交互的编程语言。

  • 考虑以下hello.html的实现,它包含了 JavaScript 和 HTML:

    <!DOCTYPE html>
    
    <!-- Demonstrates onsubmit -->
    
    <html lang="en">
        <head>
            <script>
    
                function greet()
                {
                    alert('hello, ' + document.querySelector('#name').value);
                }
    
            </script>
            <title>hello</title>
        </head>
        <body>
            <form onsubmit="greet(); return false;">
                <input autocomplete="off" autofocus id="name" placeholder="Name" type="text">
                <input type="submit">
            </form>
        </body>
    </html> 
    

    注意,这个表单使用了一个onsubmit属性来触发文件顶部的script。该脚本使用alert创建一个弹出警告。#name.value指向页面上的文本框,并获取用户输入的值。

  • 通常,将onsubmit和 JavaScript 混合使用被认为是不良的设计。我们可以将我们的代码改进如下:

    <!DOCTYPE html>
    
    <!-- Demonstrates DOMContentLoaded -->
    
    <html lang="en">
        <head>
            <script>
    
                document.addEventListener('DOMContentLoaded', function() {
                    document.querySelector('form').addEventListener('submit', function(e) {
                        alert('hello, ' + document.querySelector('#name').value);
                        e.preventDefault();
                    });
                });
    
            </script>
            <title>hello</title>
        </head>
        <body>
            <form>
                <input autocomplete="off" autofocus id="name" placeholder="Name" type="text">
                <input type="submit">
            </form>
        </body>
    </html> 
    

    注意,这个版本的代码创建了一个addEventListener来监听表单submit事件的触发。注意DOMContentLoaded确保在执行 JavaScript 之前整个页面已经加载完成。

  • 我们可以将此代码改进如下:

    <!DOCTYPE html>
    
    <!-- Demonstrates keyup and template literals -->
    
    <html lang="en">
        <head>
            <script>
    
                document.addEventListener('DOMContentLoaded', function() {
                    let input = document.querySelector('input');
                    input.addEventListener('keyup', function(event) {
                        let name = document.querySelector('p');
                        if (input.value) {
                            name.innerHTML = `hello, ${input.value}`;
                        }
                        else {
                            name.innerHTML = 'hello, whoever you are';
                        }
                    });
                });
    
            </script>
            <title>hello</title>
        </head>
        <body>
            <form>
                <input autocomplete="off" autofocus placeholder="Name" type="text">
            </form>
            <p></p>
        </body>
    </html> 
    

    注意,当用户输入一个名字时,内存中的 DOM 会动态更新。如果input中有值,在键盘的keyup事件发生时,DOM 会更新。否则,会显示默认文本。

  • JavaScript 允许你动态地读取和修改加载到内存中的 HTML 文档,这样用户就不需要重新加载来查看更改。

  • 考虑以下 HTML 代码:

    <!DOCTYPE html>
    
    <!-- Demonstrates programmatic changes to style -->
    
    <html lang="en">
        <head>
            <title>background</title>
        </head>
        <body>
            <button id="red">R</button>
            <button id="green">G</button>
            <button id="blue">B</button>
            <script>
    
                let body = document.querySelector('body');
                document.querySelector('#red').addEventListener('click', function() {
                    body.style.backgroundColor = 'red';
                });
                document.querySelector('#green').addEventListener('click', function() {
                    body.style.backgroundColor = 'green';
                });
                document.querySelector('#blue').addEventListener('click', function() {
                    body.style.backgroundColor = 'blue';
                });
    
            </script>
        </body>
    </html> 
    

    注意,JavaScript 监听特定按钮的点击事件。在点击时,页面上的某些样式属性会发生变化。body被定义为页面的主体。然后,一个事件监听器等待按钮之一被点击。然后,body.style.backgroundColor被改变。

  • 类似地,考虑以下:

    <!DOCTYPE html>
    
    <html lang="en">
        <head>
            <script>
    
                // Toggles visibility of greeting
                function blink()
                {
                    let body = document.querySelector('body');
                    if (body.style.visibility == 'hidden')
                    {
                        body.style.visibility = 'visible';
                    }
                    else
                    {
                        body.style.visibility = 'hidden';
                    }
                }
    
                // Blink every 500ms
                window.setInterval(blink, 500);
    
            </script>
            <title>blink</title>
        </head>
        <body>
            hello, world
        </body>
    </html> 
    

    这个例子在设定的时间间隔闪烁文本。注意,window.setInterval接受两个参数:一个要调用的函数和函数调用之间的等待期(以毫秒为单位)。

  • 考虑以下实现自动完成文本的 JavaScript 代码:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <title>autocomplete</title>
        </head>
    
        <body>
    
            <input autocomplete="off" autofocus placeholder="Query" type="text">
    
            <ul></ul>
    
            <script src="large.js"></script>
            <script>
    
                let input = document.querySelector('input');
                input.addEventListener('keyup', function(event) {
                    let html = '';
                    if (input.value) {
                        for (word of WORDS) {
                            if (word.startsWith(input.value)) {
                                html += `<li>${word}</li>`;
                            }
                        }
                    }
                    document.querySelector('ul').innerHTML = html;
                });
    
            </script>
    
        </body>
    </html> 
    

    这是一个自动完成的 JavaScript 实现。它从一个名为large.js的文件中获取数据(此处未展示),该文件是一个单词列表。

  • JavaScript 的功能很多,可以在JavaScript 文档中找到。

总结

在本课中,你学习了如何创建自己的 HTML 文件,为其添加样式,利用第三方框架,以及使用 JavaScript。具体来说,我们讨论了…

  • TCP/IP

  • DNS

  • HTML

  • 正则表达式

  • CSS

  • 框架

  • JavaScript

下次再见!

第九讲

原文:cs50.harvard.edu/x/notes/9/

  • 欢迎!

  • http-server

  • Flask

  • 表单

  • 模板

  • 请求方法

  • Frosh IMs

  • Flask 和 SQL

  • Cookies 和 Session

  • 购物车

  • 显示

  • APIs

  • JSON

  • 总结

欢迎光临!

  • 在之前的几周中,你已经学习了多种编程语言、技术和策略。

  • 事实上,这门课程远不止是 C 语言课程Python 课程,而更多的是一门 编程课程,这样你就可以继续追随未来的趋势。

  • 在过去的几周中,你已经学习了 如何学习 编程。

  • 今天,我们将从 HTML 和 CSS 转向结合 HTML、CSS、SQL、Python 和 JavaScript,以便你可以创建自己的 Web 应用程序。

  • 你可以考虑使用这周学到的技能来创建你的最终项目。

http-server

  • 到目前为止,你所看到的 HTML 都是预先编写和静态的。

  • 在过去,当你访问一个页面时,浏览器会下载一个 HTML 页面,你能够查看它。这些被认为是 静态 页面,因为在 HTML 中编程的内容正是用户看到的和下载到他们互联网浏览器中的内容。

  • 动态页面指的是 Python 和类似语言创建 HTML 的即时能力。因此,你可以拥有由代码根据用户输入或行为在服务器端生成的 Web 页面。

  • 你过去使用 http-server 来提供你的网页服务。今天,我们将利用一个新的服务器,它可以解析出网址并根据提供的 URL 执行操作。

  • 此外,上周,你看到了以下这样的 URL:

    https://www.example.com/folder/file.html 
    

    注意到 file.html 是位于 example.comfolder 文件夹中的一个 HTML 文件。

Flask

  • 这周,我们介绍了与 路由(如 https://www.example.com/route?key=value)交互的能力,其中特定的功能可以通过 URL 中提供的键和值在服务器上生成。

  • Flask 是一个第三方库,它允许你使用 Flask 框架或微框架在 Python 中托管 Web 应用程序。

  • 你可以在终端窗口中执行 flask run 来运行 Flask,在 cs50.dev

  • 要这样做,你需要一个名为 app.py 的文件和另一个名为 requirements.txt 的文件。app.py 包含代码,告诉 Flask 如何运行你的 Web 应用程序。requirements.txt 包含了运行 Flask 应用程序所需的库列表。

  • 这里是 requirements.txt 的一个示例:

    Flask 
    

    注意到在这个文件中只有 Flask 出现。这是因为 Flask 是运行 Flask 应用程序所必需的。

  • 这里是一个非常简单的 app.py Flask 应用程序:

    # Says hello to world by returning a string of text 
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return "hello, world" 
    

    注意到 / 路由简单地返回文本 hello, world

  • 我们还可以创建实现 HTML 的代码:

    # Says hello to world by returning a string of HTML 
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return '<!DOCTYPE html><html lang="en"><head><title>hello</title></head><body>hello, world</body></html>' 
    

    注意到它不是返回简单的文本,而是提供了 HTML。

  • 改进我们的应用程序,我们还可以通过创建一个名为 templates 的文件夹并创建一个包含以下代码的 index.html 文件来根据模板提供 HTML:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>hello</title>
        </head>
    
        <body>
            hello, {{ name }}
        </body>
    
    </html> 
    

    注意双大括号 {{ name }},它是为我们 Flask 服务器稍后提供的某个内容的占位符。

  • 然后,在 templates 文件夹所在的同一文件夹中,创建一个名为 app.py 的文件,并添加以下代码:

    # Uses request.args.get 
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        name = request.args.get("name", "world")
        return render_template("index.html", name=name) 
    

    注意,这段代码将 app 定义为 Flask 应用程序。然后,它定义了 app/ 路由,返回包含 name 参数的 index.html 内容。默认情况下,request.args.get 函数将查找用户提供的 name。如果没有提供名称,它将默认为 world。"@app.route" 通常被称为装饰器。

  • 您可以通过在终端窗口中输入 flask run 来运行这个网络应用程序。如果 Flask 没有运行,请确保上述每个文件中的语法都正确。此外,如果 Flask 无法运行,请确保您的文件组织如下:

    /templates
        index.html
    app.py
    requirements.txt 
    
  • 一旦运行起来,您将被提示点击一个链接。一旦您导航到该网页,请尝试在浏览器 URL 栏的基本 URL 中添加 ?name=[Your Name]

表单

  • 在改进我们的程序时,我们知道大多数用户不会在地址栏中输入参数。相反,程序员依赖于用户在网页上填写表单。因此,我们可以按如下方式修改 index.html

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>hello</title>
        </head>
    
        <body>
            <form action="/greet" method="get">
                <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
                <button type="submit">Greet</button>
            </form>
        </body>
    
    </html> 
    

    注意现在创建了一个表单,它接受用户的姓名,并将其传递给名为 /greet 的路由。"autocomplete" 已关闭。此外,包含文本 nameplaceholder 也被包含在内。此外,注意 meta 标签是如何被用来使网页响应式的。

  • 此外,我们还可以按如下方式修改 app.py

    # Adds a form, second route 
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return render_template("index.html")
    
    @app.route("/greet")
    def greet():
        return render_template("greet.html", name=request.args.get("name", "world")) 
    

    注意默认路径将显示一个表单,让用户输入他们的姓名。/greet 路由将 name 传递到该网页。

  • 为了完成这个实现,您需要在 templates 文件夹中创建一个名为 greet.html 的模板,如下所示:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>hello</title>
        </head>
    
        <body>
            hello, {{ name }}
        </body>
    
    </html> 
    

    注意,现在这个路由将向用户显示问候语,然后是他们的名字。

模板

  • 我们的网页 index.htmlgreet.html 有很多相同的数据。如果允许主体内容独特,但复制相同的布局从页面到页面,岂不是很好?

  • 首先,创建一个新的模板 layout.html 并编写如下代码:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>hello</title>
        </head>
    
        <body>
            {% block body %}{% endblock %}
        </body>
    
    </html> 
    

    注意到 {% block body %}{% endblock %} 允许从其他 HTML 文件中插入其他代码。

  • 然后,按如下方式修改您的 index.html

    {% extends "layout.html" %}
    
    {% block body %}
    
        <form action="/greet" method="get">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            <button type="submit">Greet</button>
        </form>
    
    {% endblock %} 
    

    注意到 {% extends "layout.html" %} 这行代码告诉服务器从哪里获取此页面的布局。然后,{% block body %}{% endblock %} 告诉要插入 layout.html 的代码。

  • 最后,按如下方式修改 greet.html

    {% extends "layout.html" %}
    
    {% block body %}
        hello, {{ name }}
    {% endblock %} 
    

    注意这段代码更短、更紧凑。

请求方法

  • 您可以想象一些场景,在这些场景中不能安全地使用 get,因为用户名和密码会出现在 URL 中。

  • 我们可以通过修改app.py来利用post方法帮助解决这个问题:

    # Switches to POST 
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/")
    def index():
        return render_template("index.html")
    
    @app.route("/greet", methods=["POST"])
    def greet():
        return render_template("greet.html", name=request.form.get("name", "world")) 
    

    注意到在/greet路由中添加了POST,并且我们使用request.form.get而不是request.args.get

  • 这告诉服务器深入查看虚拟信封,不要在 URL 中暴露post中的项目。

  • 尽管如此,我们可以通过使用单个路由来同时处理getpost来进一步改进此代码。为此,修改app.py如下:

    # Uses a single route 
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/", methods=["GET", "POST"])
    def index():
        if request.method == "POST":
            return render_template("greet.html", name=request.form.get("name", "world"))
        return render_template("index.html") 
    

    注意到getpost都是在单个路由中完成的。然而,request.method被用来根据用户请求的路由类型正确路由。

  • 因此,你可以按照以下方式修改你的index.html

    {% extends "layout.html" %}
    
    {% block body %}
    
        <form action="/" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            <button type="submit">Greet</button>
        </form>
    
    {% endblock %} 
    

    注意到表单的action已被更改。

  • 尽管如此,此代码中仍然存在一个错误。根据我们的新实现,当有人在没有输入名称的情况下填写表单时,会显示没有名称的Hello,。我们可以通过以下方式编辑app.py来改进我们的代码:

    # Moves default value to template 
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    @app.route("/", methods=["GET", "POST"])
    def index():
        if request.method == "POST":
            return render_template("greet.html", name=request.form.get("name"))
        return render_template("index.html") 
    

    注意到name=request.form.get("name"))已被更改。

  • 最后,按照以下方式修改greet.html

    {% extends "layout.html" %}
    
    {% block body %}
    
        hello,
        {% if name -%}
            {{ name }}
        {%- else -%}
            world
        {%- endif %}
    
    {% endblock %} 
    

    注意到hello, {{ name }}是如何改变以允许在没有识别到名称时输出默认值。

  • 由于我们已经更改了许多文件,你可能希望将你的最终代码与我们的最终代码进行比较。

Frosh IMs

  • Frosh IMs 或froshims是一个允许学生注册校内体育活动的网络应用程序。

  • 关闭所有与hello相关的窗口,并在终端窗口中输入mkdir froshims创建一个文件夹。然后,输入cd froshims浏览到这个文件夹。在此文件夹内,通过输入mkdir templates创建一个名为templates的目录。

  • 接下来,在froshims文件夹中,输入code requirements.txt并编写如下代码:

    Flask 
    

    如前所述,运行 Flask 应用程序需要 Flask。

  • 最后,输入code app.py并编写如下代码:

    # Implements a registration form using a select menu, validating sport server-side 
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    SPORTS = [
        "Basketball",
        "Soccer",
        "Ultimate Frisbee"
    ]
    
    @app.route("/")
    def index():
        return render_template("index.html", sports=SPORTS)
    
    @app.route("/register", methods=["POST"])
    def register():
    
        # Validate submission
        if not request.form.get("name") or request.form.get("sport") not in SPORTS:
            return render_template("failure.html")
    
        # Confirm registration
        return render_template("success.html") 
    

    注意到提供了一个failure选项,如果namesport字段填写不正确,将向用户显示错误信息。

  • 接下来,通过输入code templates/index.htmltemplates文件夹中创建一个名为index.html的文件,并编写如下代码:

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Register</h1>
        <form action="/register" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            <select name="sport">
                <option disabled selected value="">Sport</option>
                {% for sport in sports %}
                    <option value="{{ sport }}">{{ sport }}</option>
                {% endfor %}
            </select>
            <button type="submit">Register</button>
        </form>
    {% endblock %} 
    
  • 接下来,通过输入code templates/layout.html创建一个名为layout.html的文件,并编写如下代码:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>froshims</title>
        </head>
    
        <body>
            {% block body %}{% endblock %}
        </body>
    
    </html> 
    
  • 第四,在templates目录下创建一个名为success.html的文件,如下所示:

    {% extends "layout.html" %}
    
    {% block body %}
        You are registered!
    {% endblock %} 
    
  • 最后,在templates目录下创建一个名为failure.html的文件,如下所示:

    {% extends "layout.html" %}
    
    {% block body %}
        You are not registered!
    {% endblock %} 
    
  • 执行flask run并检查此阶段的程序。

  • 你可以想象我们如何使用单选按钮查看各种注册选项。我们可以通过以下方式改进index.html

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Register</h1>
        <form action="/register" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            {% for sport in sports %}
                <input name="sport" type="radio" value="{{ sport }}"> {{ sport }}
            {% endfor %}
            <button type="submit">Register</button>
        </form>
    {% endblock %} 
    

    注意到type已被更改为radio

  • 再次执行flask run,你可以看到界面现在已更改。

  • 你可以想象我们如何接受许多不同注册者的注册。我们可以通过以下方式改进app.py

    # Implements a registration form, storing registrants in a dictionary, with error messages 
    from flask import Flask, redirect, render_template, request
    
    app = Flask(__name__)
    
    REGISTRANTS = {}
    
    SPORTS = [
        "Basketball",
        "Soccer",
        "Ultimate Frisbee"
    ]
    
    @app.route("/")
    def index():
        return render_template("index.html", sports=SPORTS)
    
    @app.route("/register", methods=["POST"])
    def register():
    
        # Validate name
        name = request.form.get("name")
        if not name:
            return render_template("error.html", message="Missing name")
    
        # Validate sport
        sport = request.form.get("sport")
        if not sport:
            return render_template("error.html", message="Missing sport")
        if sport not in SPORTS:
            return render_template("error.html", message="Invalid sport")
    
        # Remember registrant
        REGISTRANTS[name] = sport
    
        # Confirm registration
        return redirect("/registrants")
    
    @app.route("/registrants")
    def registrants():
        return render_template("registrants.html", registrants=REGISTRANTS) 
    

    注意到使用了一个名为 REGISTRANTS 的字典来记录 REGISTRANTS[name] 选定的 sport。同时,注意 registrants=REGISTRANTS 将字典传递给此模板。

  • 此外,我们可以实现 error.html

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Error</h1>
        <p>{{ message }}</p>
        <img alt="Grumpy Cat" src="/static/cat.jpg">
    {% endblock %} 
    
  • 此外,创建一个新的模板,名为 registrants.html,如下所示:

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Registrants</h1>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Sport</th>
                </tr>
            </thead>
            <tbody>
                {% for name in registrants %}
                    <tr>
                        <td>{{ name }}</td>
                        <td>{{ registrants[name] }}</td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    {% endblock %} 
    

    注意到 {% for name in registrants %}...{% endfor %} 会遍历每个注册者。能够在动态网页上迭代是非常强大的功能!

  • 最后,在 app.py 所在的同一文件夹中创建一个名为 static 的文件夹。在那里,上传以下文件:一个 的文件。

  • 执行 flask run 并与该应用程序互动。

  • 现在你已经拥有了一个网络应用程序!然而,存在一些安全漏洞!因为所有内容都在客户端,攻击者可以更改 HTML 并黑客网站。此外,如果服务器关闭,这些数据将不会持久化。我们是否有办法让数据在服务器重启时也能持久化?

Flask 和 SQL

  • 正如我们所见,Python 可以与 SQL 数据库交互,我们可以结合 Flask、Python 和 SQL 的力量来创建一个数据将持久化的网络应用程序!

  • 要实现这一点,你需要采取多个步骤。

  • 首先,将以下 SQL 数据库下载到你的 froshims 文件夹中。

  • 在终端中执行 sqlite3 froshims.db 并输入 .schema 以查看数据库文件的内容。进一步输入 SELECT * FROM registrants; 以了解内容。你会注意到文件中目前没有任何注册信息。

  • 接下来,按如下方式修改 requirements.txt

    cs50
    Flask 
    
  • 按如下方式修改 index.html

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Register</h1>
        <form action="/register" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            {% for sport in sports %}
                <input name="sport" type="checkbox" value="{{ sport }}"> {{ sport }}
            {% endfor %}
            <button type="submit">Register</button>
        </form>
    {% endblock %} 
    
  • 按如下方式修改 layout.html

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>froshims</title>
        </head>
    
        <body>
            {% block body %}{% endblock %}
        </body>
    
    </html> 
    
  • 确保 error.html 显示如下:

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Error</h1>
        <p>{{ message }}</p>
        <img alt="Grumpy Cat" src="/static/cat.jpg">
    {% endblock %} 
    
  • 修改 registrants.html 以如下所示:

    {% extends "layout.html" %}
    
    {% block body %}
        <h1>Registrants</h1>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Sport</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                {% for registrant in registrants %}
                    <tr>
                        <td>{{ registrant.name }}</td>
                        <td>{{ registrant.sport }}</td>
                        <td>
                            <form action="/deregister" method="post">
                                <input name="id" type="hidden" value="{{ registrant.id }}">
                                <button type="submit">Deregister</button>
                            </form>
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    {% endblock %} 
    

    注意到包含了一个隐藏值 registrant.id,这样就可以在 app.py 中稍后使用这个 id

  • 最后,按如下方式修改 app.py

    # Implements a registration form, storing registrants in a SQLite database, with support for deregistration 
    from cs50 import SQL
    from flask import Flask, redirect, render_template, request
    
    app = Flask(__name__)
    
    db = SQL("sqlite:///froshims.db")
    
    SPORTS = [
        "Basketball",
        "Soccer",
        "Ultimate Frisbee"
    ]
    
    @app.route("/")
    def index():
        return render_template("index.html", sports=SPORTS)
    
    @app.route("/deregister", methods=["POST"])
    def deregister():
    
        # Forget registrant
        id = request.form.get("id")
        if id:
            db.execute("DELETE FROM registrants WHERE id = ?", id)
        return redirect("/registrants")
    
    @app.route("/register", methods=["POST"])
    def register():
    
        # Validate name
        name = request.form.get("name")
        if not name:
            return render_template("error.html", message="Missing name")
    
        # Validate sports
        sports = request.form.getlist("sport")
        if not sports:
            return render_template("error.html", message="Missing sport")
        for sport in sports:
            if sport not in SPORTS:
                return render_template("error.html", message="Invalid sport")
    
        # Remember registrant
        for sport in sports:
            db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport)
    
        # Confirm registration
        return redirect("/registrants")
    
    @app.route("/registrants")
    def registrants():
        registrants = db.execute("SELECT * FROM registrants")
        return render_template("registrants.html", registrants=registrants) 
    

    注意到使用了 cs50 库。包含了一个用于 registerpost 方法的路由。这个路由将获取注册表单中的姓名和运动项目,并执行一个 SQL 查询将 namesport 添加到 registrants 表中。deregister 路由将执行一个 SQL 查询,获取用户的 id 并利用这些信息注销该个人。

  • 你可以执行 flask run 并检查结果。

  • 如果你想下载我们的 froshims 实现,你可以在这里下载。

  • 你可以在Flask 文档中了解更多关于 Flask 的信息。

Cookies 和 Session

  • app.py 被视为控制器视图被认为是用户所看到的内容。模型是数据存储和操作的方式。三者合称为 MVC(模型,视图,控制器)。

  • 虽然之前的 froshims 实现从管理角度来看很有用,其中后台管理员可以向数据库添加和删除个人,但可以想象这段代码在公共服务器上实现并不安全。

  • 首先,不良分子可能会通过点击注销按钮代表其他用户做出决定——实际上是从服务器删除他们记录的答案。

  • 像谷歌这样的网络服务使用登录凭证来确保用户只能访问正确的数据。

  • 实际上,我们可以使用 cookies 来实现这一点。Cookies 是存储在您计算机上的小文件,这样您的计算机就可以与服务器通信,并有效地表示,“我是一个已经登录的授权用户。” 这种通过 cookie 的授权称为 会话

  • Cookies 可以按照以下方式存储:

    GET / HTTP/2
    Host: accounts.google.com
    Cookie: session=value 
    

    在这里,一个 session id 被存储,并带有特定的 value,表示该会话。

  • 以最简单的方式,我们可以通过创建一个名为 login 的文件夹,然后添加以下文件来实现这一点。

  • 首先,创建一个名为 requirements.txt 的文件,其内容如下:

    Flask
    Flask-Session 
    

    注意,除了Flask,我们还包含了Flask-Session,这是支持登录会话所必需的。

  • 第二,在 templates 文件夹中创建一个名为 layout.html 的文件,其内容如下:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>login</title>
        </head>
    
        <body>
            {% block body %}{% endblock %}
        </body>
    
    </html> 
    

    注意这提供了一个非常简单的布局,包括标题和正文。

  • 第三,在templates文件夹中创建一个名为index.html的文件,其内容如下:

    {% extends "layout.html" %}
    
    {% block body %}
    
        {% if name -%}
            You are logged in as {{ name }}. <a href="/logout">Log out</a>.
        {%- else -%}
            You are not logged in. <a href="/login">Log in</a>.
        {%- endif %}
    
    {% endblock %} 
    

    注意这个文件会检查 session["name"] 是否存在(在下面的 app.py 中进一步阐述)。如果存在,它将显示欢迎信息。如果不存在,它将建议您浏览到登录页面。

  • 第四,创建一个名为 login.html 的文件,并添加以下代码:

    {% extends "layout.html" %}
    
    {% block body %}
    
        <form action="/login" method="post">
            <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
            <button type="submit">Log In</button>
        </form>
    
    {% endblock %} 
    

    注意这是基本登录页面的布局。

  • 最后,创建一个名为 app.py 的文件,并编写以下代码:

    from flask import Flask, redirect, render_template, request, session
    from flask_session import Session
    
    # Configure app app = Flask(__name__)
    
    # Configure session app.config["SESSION_PERMANENT"] = False
    app.config["SESSION_TYPE"] = "filesystem"
    Session(app)
    
    @app.route("/")
    def index():
        return render_template("index.html", name=session.get("name"))
    
    @app.route("/login", methods=["GET", "POST"])
    def login():
        if request.method == "POST":
            session["name"] = request.form.get("name")
            return redirect("/")
        return render_template("login.html")
    
    @app.route("/logout")
    def logout():
        session.clear()
        return redirect("/") 
    

    注意文件顶部的修改后的 导入,包括 session,这将允许您支持会话。最重要的是,注意 session["name"]loginlogout 路由中的使用。login 路由会将提供的登录名分配给 session["name"]。然而,在 logout 路由中,注销是通过清除 session 的值来实现的。

  • session 抽象允许您确保只有特定用户可以访问我们应用程序中的特定数据和功能。它允许您确保没有人代表另一个用户行事,无论是好是坏!

  • 如果您愿意,您可以下载我们的实现

  • 你可以在Flask 文档中了解更多关于会话的信息。

购物车

  • 接下来,我们将通过一个最终示例来展示如何利用 Flask 的会话启用功能。

  • 我们检查了 app.py 中的 store 代码。以下代码被展示:

    from cs50 import SQL
    from flask import Flask, redirect, render_template, request, session
    from flask_session import Session
    
    # Configure app app = Flask(__name__)
    
    # Connect to database db = SQL("sqlite:///store.db")
    
    # Configure session app.config["SESSION_PERMANENT"] = False
    app.config["SESSION_TYPE"] = "filesystem"
    Session(app)
    
    @app.route("/")
    def index():
        books = db.execute("SELECT * FROM books")
        return render_template("books.html", books=books)
    
    @app.route("/cart", methods=["GET", "POST"])
    def cart():
    
        # Ensure cart exists
        if "cart" not in session:
            session["cart"] = []
    
        # POST
        if request.method == "POST":
            book_id = request.form.get("id")
            if book_id:
                session["cart"].append(book_id)
            return redirect("/cart")
    
        # GET
        books = db.execute("SELECT * FROM books WHERE id IN (?)", session["cart"])
        return render_template("cart.html", books=books) 
    

    注意到cart是通过列表实现的。可以使用books.html中的Add to Cart按钮向此列表添加项目。点击此类按钮时,将调用post方法,其中将项目id附加到cart上。在查看购物车时,调用get方法,执行 SQL 以显示购物车中的书籍列表。

  • 我们还看到了books.html的内容:

    {% extends "layout.html" %}
    
    {% block body %}
    
        <h1>Books</h1>
        {% for book in books %}
            <h2>{{ book["title"] }}</h2>
            <form action="/cart" method="post">
                <input name="id" type="hidden" value="{{ book['id'] }}">
                <button type="submit">Add to Cart</button>
            </form>
        {% endfor %}
    
    {% endblock %} 
    

    注意到这是如何通过for book in books为每本书创建Add to Cart功能的。

  • 你可以在源代码中查看驱动此flask实现的其余文件。

显示

  • 我们在app.py中查看了一个名为shows的预设计程序:

    # Searches for shows using LIKE 
    from cs50 import SQL
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    db = SQL("sqlite:///shows.db")
    
    @app.route("/")
    def index():
        return render_template("index.html")
    
    @app.route("/search")
    def search():
        shows = db.execute("SELECT * FROM shows WHERE title LIKE ?", "%" + request.args.get("q") + "%")
        return render_template("search.html", shows=shows) 
    

    注意到search路由允许通过一种方式来搜索show。此搜索查找与用户提供的标题LIKE匹配的标题。

  • 我们还检查了index.html

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>shows</title>
        </head>
    
        <body>
    
            <input autocomplete="off" autofocus placeholder="Query" type="text">
    
            <ul></ul>
    
            <script>
                let input = document.querySelector('input');
                input.addEventListener('input', async function() {
                    let response = await fetch('/search?q=' + input.value);
                    let shows = await response.json();
                    let html = '';
                    for (let id in shows) {
                        let title = shows[id].title.replace('<', '&lt;').replace('&', '&amp;');
                        html += '<li>' + title + '</li>';
                    }
                    document.querySelector('ul').innerHTML = html;
                });
            </script>
    
        </body>
    
    </html> 
    

    注意到 JavaScript script创建了一个自动完成的实现,其中匹配input的标题会被显示出来。

  • 你可以在源代码中查看此实现的其他文件。

API

  • 应用程序程序接口API是一系列规范,允许你与另一个服务进行交互。例如,我们可以利用 IMDB 的 API 来与其数据库进行交互。我们甚至可以集成用于处理从服务器下载的特定类型数据的 API。

  • 在对shows进行改进的同时,查看app.py的改进,我们看到了以下内容:

    # Searches for shows using Ajax 
    from cs50 import SQL
    from flask import Flask, render_template, request
    
    app = Flask(__name__)
    
    db = SQL("sqlite:///shows.db")
    
    @app.route("/")
    def index():
        return render_template("index.html")
    
    @app.route("/search")
    def search():
        q = request.args.get("q")
        if q:
            shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%")
        else:
            shows = []
        return render_template("search.html", shows=shows) 
    

    注意到search路由执行了一个 SQL 查询。

  • 查看search.html,你会注意到它非常简单:

    {% for show in shows %}
        <li>{{ show["title"] }}</li>
    {% endfor %} 
    

    注意到它提供了一个项目符号列表。

  • 最后,查看index.html,注意到AJAX代码被用来驱动搜索:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>shows</title>
        </head>
    
        <body>
    
            <input autocomplete="off" autofocus placeholder="Query" type="search">
    
            <ul></ul>
    
            <script>
                let input = document.querySelector('input');
                input.addEventListener('input', async function() {
                    let response = await fetch('/search?q=' + input.value);
                    let shows = await response.text();
                    document.querySelector('ul').innerHTML = shows;
                });
            </script>
    
        </body>
    
    </html> 
    

    注意到使用事件监听器动态查询服务器以提供与提供的标题匹配的列表。这将定位 HTML 中的ul标签并相应地修改网页以包含匹配的列表。

  • 你可以在AJAX 文档中了解更多信息。

JSON

  • JavaScript 对象表示法JSON是一个包含键和值的字典文本文件。这是一种原始且对计算机友好的方式来获取大量数据。

  • JSON 是从服务器获取数据的一种非常有用的方式。

  • 你可以在我们共同检查的index.html中看到这一操作:

    <!DOCTYPE html>
    
    <html lang="en">
    
        <head>
            <meta name="viewport" content="initial-scale=1, width=device-width">
            <title>shows</title>
        </head>
    
        <body>
    
            <input autocomplete="off" autofocus placeholder="Query" type="text">
    
            <ul></ul>
    
            <script>
                let input = document.querySelector('input');
                input.addEventListener('input', async function() {
                    let response = await fetch('/search?q=' + input.value);
                    let shows = await response.json();
                    let html = '';
                    for (let id in shows) {
                        let title = shows[id].title.replace('<', '&lt;').replace('&', '&amp;');
                        html += '<li>' + title + '</li>';
                    }
                    document.querySelector('ul').innerHTML = html;
                });
            </script>
    
        </body>
    
    </html> 
    

    虽然上述内容可能有些晦涩,但它为你提供了一个起点,让你可以自己研究 JSON,看看它如何在你的网络应用程序中实现。

  • 此外,我们还检查了app.py以了解如何获取 JSON 响应:

    # Searches for shows using Ajax with JSON 
    from cs50 import SQL
    from flask import Flask, jsonify, render_template, request
    
    app = Flask(__name__)
    
    db = SQL("sqlite:///shows.db")
    
    @app.route("/")
    def index():
        return render_template("index.html")
    
    @app.route("/search")
    def search():
        q = request.args.get("q")
        if q:
            shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%")
        else:
            shows = []
        return jsonify(shows) 
    

    注意到jsonify是如何被用来将结果转换为当代网络应用可接受的易读格式的。

  • 你可以在JSON 文档中了解更多信息。

  • 总结来说,你现在可以使用 Python、Flask、HTML 和 SQL 来完成自己的网络应用程序。

总结

在本节课中,你学习了如何利用 Python、SQL 和 Flask 来创建 Web 应用程序。具体来说,我们讨论了……

  • Flask

  • 表单

  • 模板

  • 请求方法

  • Flask 和 SQL

  • Cookie 和会话

  • API

  • JSON

下次再见,我们将在这里举行本学期的最后一堂课,地点是Sanders Theatre

posted @ 2025-11-08 11:25  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报