Diferències
Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.
| Ambdós costats versió prèvia Revisió prèvia | |||
| info:cursos:pue:python-pcpp1:m4:1.1 [08/01/2024 20:13] – mate | info:cursos:pue:python-pcpp1:m4:1.1 [08/01/2024 20:15] (actual) – mate | ||
|---|---|---|---|
| Línia 163: | Línia 163: | ||
| Okay. Taking such a dose of theory requires some practice as soon as possible. Let's do it. | Okay. Taking such a dose of theory requires some practice as soon as possible. Let's do it. | ||
| - | === How to fetch a document from a server using Python | ||
| - | We are going to write our first program making use of network sockets. Of course, we'll harness Python for this purpose. | ||
| - | Here are our goals: | ||
| - | |||
| - | * we want to write **a program which reads the address of a WWW site** (e.g., pythoninstitute.org) using the standard '' | ||
| - | * the program **outputs the document** to the screen; | ||
| - | * the program **uses TCP to connect to the HTTP server**. | ||
| - | |||
| - | Our program has to perform the following steps: | ||
| - | |||
| - | - **create a new socket** able to handle connection-oriented transmissions based on TCP; | ||
| - | - **connect the socket to the HTTP server** of a given address; | ||
| - | - **send a request to the server** (the server wants to know what we want from it) | ||
| - | - **receive the server' | ||
| - | - **close the socket** (end the connection) | ||
| - | This is our road map. Let's follow the route. | ||
| - | |||
| - | {{ : | ||
| - | |||
| - | ==== Importing a socket | ||
| - | We are in need - we need a socket. How do we obtain a socket? Can we order it from an Internet store? Is it free? | ||
| - | |||
| - | Yes, it's free. As you probably suspect, we need a specialized module. Python offers just such a module. You won't be surprised if we tell you that the module is named socket, will you? | ||
| - | |||
| - | This is what we'll put at the top of our code: | ||
| - | <code python> | ||
| - | import socket | ||
| - | </ | ||
| - | |||
| - | ==== Obtaining user input | ||
| - | We also need **the name of the HTTP server** we're going to connect to. In fact, it's not our problem. The user knows it better. Let's ask him or her: | ||
| - | <code python> | ||
| - | import socket | ||
| - | |||
| - | server_addr = input(" | ||
| - | </ | ||
| - | |||
| - | The user input may can take two different forms: | ||
| - | |||
| - | * it can be **the domain name of the server** (like // | ||
| - | * it can be **the IP address of the server** (like 87.98.235.184), | ||
| - | It may sound cynical - it's not our problem which of these two ways our users choose. They know better. The customer is always right. | ||
| - | |||
| - | ==== The socket module: creating a socket | ||
| - | The '' | ||
| - | |||
| - | We can say that TCP/IP is interesting for us only to the extent that it is able to transport HTTP traffic, and HTTP is interesting for us only to the extent that it is able to act as a relay for REST. If you want to get fully accustomed with networks, you may need to continue your reading using another of our courses. | ||
| - | |||
| - | The socket module provides a class named '' | ||
| - | <code python> | ||
| - | import socket | ||
| - | |||
| - | server_addr = input(" | ||
| - | sock = socket.socket(socket.AF_INET, | ||
| - | </ | ||
| - | |||
| - | As you can see, the constructor takes two arguments, both declared within the module. Let us tell you about them: | ||
| - | |||
| - | * the former argument is a domain code (we may use the '' | ||
| - | * the latter argument is a socket type code (we may use the '' | ||
| - | |||
| - | Such a socket is prepared to work on top of TCP protocol - it's the default socket configuration. | ||
| - | |||
| - | If you want to create a socket to cooperate with another protocol, like UDP, you will need to use a different constructor syntax. | ||
| - | |||
| - | As you can see, the newly created socket object will be referenced by a variable named '' | ||
| - | |||
| - | === Connecting to a server | ||
| - | If we use a socket on the client' | ||
| - | |||
| - | The configured socket (just like ours) is able to be connected to its counterpart on the server' | ||
| - | |||
| - | <code python> | ||
| - | import socket | ||
| - | |||
| - | server_addr = input(" | ||
| - | sock = socket.socket(socket.AF_INET, | ||
| - | sock.connect((server_addr, | ||
| - | </ | ||
| - | |||
| - | The '' | ||
| - | |||
| - | Note: we make use of the variant where the two values are passed to the method as elements of a tuple. This is why you see two pairs of parentheses there. Omitting one of them will obviously cause an error. | ||
| - | |||
| - | Note: the form of the target service address (a pair consisting of the actual address and port number) is **specific for the INET domain**. Don't expect it to look the same in other domains. | ||
| - | |||
| - | You may ask - why 80? Can I put something else instead of this? No, you can’t. 80 is a well-known service number for HTTP. Any Internet browser will try to connect to port number 80 by default, so we do it, too. | ||
| - | |||
| - | Is it possible that the connection attempt will fail? Of course it is. There are lots of possible reasons: a malformed address of the service, a non-existent server, a connection error, and more. How we can discover such unpleasant events? | ||
| - | |||
| - | If something goes wrong, the '' | ||
| - | |||
| - | Yes, we know. The awakening from this dream can be painful. | ||
| - | |||
| - | The connection is ready. The server has accepted our connection and is very curious about what it will hear from us. Don't let it wait too long. | ||
| - | |||
| - | But... what do we really want to tell the server anyway? How do we talk to the HTTP server to be sure that it understands us? We have to speak in HTTP, of course. | ||
| - | |||
| - | ==== The GET method | ||
| - | The HTTP protocol is one of the simplest Internet protocols, but it is still too complex to discuss fully here. For now, we'll tell you how to get a root document from the WWW site. Of course, we'll tell you more about it later. | ||
| - | |||
| - | A conversation with the HTTP server consists of **requests (sent by the client) and responses (sent by the server)**. | ||
| - | |||
| - | HTTP defines a set of acceptable requests - these are **the request methods or HTTP words**. The method asking the server to send a particular document of a given name is called GET (it's rather self-explanatory, | ||
| - | |||
| - | To get a root document from a site named // | ||
| - | <code ; output> | ||
| - | GET / HTTP/ | ||
| - | Host: www.site.com\r\n | ||
| - | Connection: close\r\n | ||
| - | \r\n | ||
| - | </ | ||
| - | |||
| - | The '' | ||
| - | |||
| - | * a line containing the method name (i.e., '' | ||
| - | * a line containing the name of the site (e.g., // | ||
| - | * a line containing a parameter named '' | ||
| - | * an empty line is **a request terminator**. | ||
| - | |||
| - | It doesn’t look very clear, but it doesn' | ||
| - | |||
| - | Okay, we know now that HTTP won't be our favourite language, but how we can send such a request to the server? It's simple. We have to invoke a method from within the socket object. | ||
| - | |||
| - | Its name is... can you guess? | ||
| - | |||
| - | ==== Requesting a document from a server | ||
| - | Yes, it's send - look at how we combine it with our code from the previous lesson: | ||
| - | <code ; output> | ||
| - | sock.send(b" | ||
| - | bytes(server_addr, | ||
| - | b" | ||
| - | </ | ||
| - | |||
| - | The '' | ||
| - | |||
| - | Note: the bytes' second argument specifies the encoding used to store the server' | ||
| - | |||
| - | The action performed by the '' | ||
| - | |||
| - | Fortunately, | ||
| - | |||
| - | Of course, if anything inside this complex mechanism fails, '' | ||
| - | |||
| - | Anyway, the die is cast. The request has been sent. What can we expect from the server? | ||
| - | |||
| - | If the server is functional and there is a root document ready to send to us, we are allowed to receive it. We'll do it now without hesitation. | ||
| - | |||
| - | Look at the final version of our code. We've provided it in the editor. | ||
| - | |||
| - | <code python> | ||
| - | import socket | ||
| - | |||
| - | server_addr = input(" | ||
| - | sock = socket.socket(socket.AF_INET, | ||
| - | sock.connect((server_addr, | ||
| - | sock.send(b" | ||
| - | bytes(server_addr, | ||
| - | b" | ||
| - | </ | ||
| - | |||
| - | ==== | ||
| - | Requesting a document from a server: continued | ||
| - | The '' | ||
| - | |||
| - | <code python> | ||
| - | import socket | ||
| - | |||
| - | server_addr = input(" | ||
| - | sock = socket.socket(socket.AF_INET, | ||
| - | sock.connect((server_addr, | ||
| - | sock.send(b" | ||
| - | bytes(server_addr, | ||
| - | b" | ||
| - | reply = sock.recv(10000) | ||
| - | </ | ||
| - | |||
| - | The argument specifies the maximal acceptable length of the data to be received. If the server' | ||
| - | |||
| - | You will need to invoke '' | ||
| - | |||
| - | There are lots of bad things which can spoil our game. For example, the server may not want to talk with us. | ||
| - | |||
| - | The transmission may cause some errors, too. All these fatalities will raise exceptions. | ||
| - | |||
| - | What next? | ||
| - | |||
| - | ==== Closing the connection | ||
| - | As we want to neither send nor receive anything more, we ought to announce it to the server. We will do it in a very simple form, just like here: | ||
| - | |||
| - | <code python> | ||
| - | import socket | ||
| - | |||
| - | server_addr = input(" | ||
| - | sock = socket.socket(socket.AF_INET, | ||
| - | sock.connect((server_addr, | ||
| - | sock.send(b" | ||
| - | bytes(server_addr, | ||
| - | b" | ||
| - | reply = sock.recv(10000) | ||
| - | sock.shutdown(socket.SHUT_RDWR) | ||
| - | </ | ||
| - | |||
| - | Invoking '' | ||
| - | |||
| - | Thanks to that, the server is aware of our intentions. | ||
| - | |||
| - | The following function arguments say more about our views for the future: | ||
| - | |||
| - | * '' | ||
| - | * '' | ||
| - | * '' | ||
| - | Is there anything more we should do now? | ||
| - | |||
| - | As our '' | ||
| - | |||
| - | Some would say that closing it explicitly is an exaggerated diligence. We don't share this view and prefer to close the connection by expressing it literally. | ||
| - | |||
| - | The parameterless '' | ||
| - | |||
| - | <code python> | ||
| - | import socket | ||
| - | |||
| - | server_addr = input(" | ||
| - | sock = socket.socket(socket.AF_INET, | ||
| - | sock.connect((server_addr, | ||
| - | sock.send(b" | ||
| - | bytes(server_addr, | ||
| - | b" | ||
| - | reply = sock.recv(10000) | ||
| - | sock.shutdown(socket.SHUT_RDWR) | ||
| - | sock.close() | ||
| - | </ | ||
| - | |||
| - | Okay. We've received something. Is it worth seeing with our own eyes? We won't see until we see. | ||
| - | |||
| - | ==== What did we get? | ||
| - | Don't expect our code to be able to display the received document in the same way as the Internet browser shows it to you. A code able to do anything like this won't fit on your screen. | ||
| - | |||
| - | Moreover, we don't want to write a new browser. We just want to check whether the data we received looks reasonable. | ||
| - | |||
| - | We'll do it in the simplest (but a very elegant) way - we'll just print it out using the built-in '' | ||
| - | |||
| - | This is why the last line of our code look as follows: '' | ||
| - | |||
| - | Our code is complete - let's see it in all its glory in the editor window. | ||
| - | |||
| - | <code python> | ||
| - | import socket | ||
| - | |||
| - | server_addr = input(" | ||
| - | sock = socket.socket(socket.AF_INET, | ||
| - | sock.connect((server_addr, | ||
| - | sock.send(b" | ||
| - | bytes(server_addr, | ||
| - | b" | ||
| - | reply = sock.recv(10000) | ||
| - | sock.shutdown(socket.SHUT_RDWR) | ||
| - | sock.close() | ||
| - | print(repr(reply)) | ||
| - | </ | ||
| - | |||
| - | ==== What can we expect from the server' | ||
| - | If everything went successfully (the user entered a valid address, the Internet worked as expected, the server was willing to cooperate, etc.) you may see something like this on your screen: | ||
| - | <code ; output> | ||
| - | What server do you want to connect to? www.site.com | ||
| - | b' | ||
| - | </ | ||
| - | |||
| - | Does it look friendly? Of course not. So... what do we see here actually? | ||
| - | |||
| - | In fact, we see two separate parts: | ||
| - | |||
| - | * the first is the response header. We'll tell you a little secret - the topmost line is the most important, as is says whether the server sent back the requested document or not. Look, there is a very significant three-digit number: '' | ||
| - | * the document. Yes, this is the place where it starts. It may very bloated (it usually is) and we don't want to present it in full.\\It' | ||
| - | |||
| - | We'll to dive into something else - what happens if something goes wrong. The Internet is an unfriendly space - many things may fail. For example... | ||
| - | |||
| - | ==== Entering a non-existing/ | ||
| - | The user has entered a non-existent or malformed address (no matter whether it’s expressed as a domain name or IP address). What will happen then? You can see such accidents here: | ||
| - | <code ; output> | ||
| - | What server do you want to connect to? a.non.existent.name | ||
| - | Traceback (most recent call last): | ||
| - | File " | ||
| - | sock.connect((srvaddr, | ||
| - | socket.gaierror: | ||
| - | |||
| - | What server do you want to connect to? anonexistentname | ||
| - | Traceback (most recent call last): | ||
| - | File " | ||
| - | sock.connect((srvaddr, | ||
| - | socket.gaierror: | ||
| - | </ | ||
| - | |||
| - | The connect function throws an exception named '' | ||
| - | |||
| - | As you probably guess, '' | ||
| - | |||
| - | Note: '' | ||
| - | |||
| - | As you can see, the exception is the same, but it carries different accompanied information about the reasons. | ||
| - | |||
| - | It is also possible that a server of a specified address exists and it is working but doesn' | ||
| - | |||
| - | If you want to provoke such an event, replace 80 in the '' | ||
| - | |||
| - | We bet you'll see something like this: | ||
| - | <code ; output> | ||
| - | What server do you want to connect to? dedicated.server | ||
| - | Traceback (most recent call last): | ||
| - | File " | ||
| - | sock.connect((srvaddr, | ||
| - | ConnectionRefusedError: | ||
| - | </ | ||
| - | |||
| - | As you can see, the exception is different than before - its name announces that the server has refused our connection. In other words, the server isn't intended to provide the services we want to utilize. | ||
| - | |||
| - | ==== The socket.timeout exception | ||
| - | The last exception we want to tell you about is '' | ||
| - | |||
| - | If you really want to induce such an exception, you'll have to do something naughty like break the network connection in the middle of a transfer, or shut down the server at a precisely chosen moment. We don't want you to do this. We want you to remember that such a situation may occur. Be prepared. Rust never sleeps. | ||
| - | |||
| - | Armed with a basic knowledge about TCP/IP, we are ready to continue our trip. See you at the next stop. | ||