How to send image using socketserver

I was able to send static files to the client without problems through the socketserver python module. Now I have problems when sending image files, below is the code :

self.header = \
    "Content-Type: image/png \n" \
    "Content-Language: pt-BR"

# define type file
self.tile = open("wsi.png", "rb")
# read file in format binary
self.sile = self.tile.read()

self.rine = self.pov + " " + "200 OK \n" + self.header + '\n\n' + self.sile
self.request.sendall(self.rine)

Error msg : TypeError: can only concatenate str (not “bytes”) to str

Python distinguishes between text (str) and binary data (bytes). The
image data are stored in a single bytes instance. You’re joining it with
str instances.

Make those bytes instead:

self.header = (
    b"Content-Type: image/png \n"
    b"Content-Language: pt-BR"
)


self.rine = self.pov + b" " + b"200 OK \n" + self.header + b'\n\n' + self.sile

and also self.pov, whatever that is.

Note that the above b"blahblah" stuff works only because the headers etc
are all ASCII compatible. (And HTTP etc is bytes anyway, but I digress).

I suspect that whatever self.request is, its sendall() method accepts
str or bytes, and converts str to bytes (because the stuff sent over the
network is always bytes). So this issue was hidden from you when you
read a text file and worked entirely in str.

BTW, network headers usually terminate in “\r\n”, not “\n”. Most servers
accept a plain “\n”, but it is not actually correct.

Cheers,
Cameron Simpson cs@cskk.id.au

Processing output generated this error: TypeError: can only concatenate str (not “bytes”) to str

As I explained, all the values need to be bytes. Clearly that’s not
the case.

You need to print the types of everything you’re trying to concatenate,
eg:

print("pov:", type(self.pov))

before the points where things fail.

The traceback of the error will tell you exactly where the probem first
occurs - put relevant prints just before that line.

Cheers,
Cameron Simpson cs@cskk.id.au

“\n” is the class Unix/linux style for new line. “\r\n” is the default Windows style for line separator.

But you’re sending HTTP, not writing a local text file. Go read RFC2616.

This has nothing to do with text file line separators on various
platforms.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

To solve this data typing problem I converted everything to string type so when sending the response to the browser it is sent with data type in bytes, it worked for status code, header, text message, except for the file type of image where I change the min-type to ( image/png ), but no visibility in the browser of the .png file ?

...
# Define variable of the type string
self.s_status = self.pro + "200 OK \n"

# define tuple for http header, "Content-type: image/png \r",
self.t_header = (
    "Content-Type: image/png \n",
    "Content-Language: pt-BR \n",
    "Server: Action v0.0.1 \n\n"
)
# convert tuple for string
self.s_header = "".join(self.t_header)

# access and open file of figure
self.file = open("wsi.png", "rb")
# read file of figure
self.b_file = self.file.read()
# convert for string
self.s_file = str(self.b_file, "utf-8")

# Define variable of the type string
self.s_txt = "Webstrucs Action"

self.s_reply = self.s_status + self.s_header + self.s_file

self.request.sendall(bytes(self.s_reply, "utf-8"))
...

To solve this data typing problem I converted everything to string type
so when sending the response to the browser it is sent with data type
in bytes, it worked for status code, header, text message, except for
the file type of image where I change the min-type to ( image/png ),
but no visibility in the browser of the .png file ?

Just converting everything to str via arbitrary methods is not the
correct way to fix your probems. It only hides them or pushes the side
effects further away from where you’re doing things (for example, by
sending junk data to a browser which doesn’t render it).

You’ve gone:

self.s_file = str(self.b_file, "utf-8")

This DOES NOT do what you seem to imagine. This claims that b_file is a
bytes object which encodes some text in utf-8. And then deocdes it. I
am astonished that this did not result in a UnicodeDecodeError exception
for you - you must have been very lucky with your image file data, which
by chance must resemble utf-8 data, at least in terms of being a legal
sequence of bytes for utf-8. There’s every chance this should fail.
Unless your test file is maybe empty. Example:

>>> str(b'abc\x81', 'utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 3: invalid start byte

This is not what you should be doing.

There are 2 approaches which are valid:

  • what I suggested, which is to conduct the HTTP response in bytes
    entirely
  • your approach, to conduct it as though HTTP were text

If you choose the latter you need to encode the binary data as text in
a form which the receiving browser can decode. For example, base64.

First, let’s be clear about what you’re doing above: you are taking
binary bytes and trying to decode them as though they contain utf-8
encoded text, to get some text. FOr image data, that is nonsense, even
if the decode succeeds - it is not text and the decode is not
meaningful. Then you’re passing that text (str) to request.sendall().
That method then encodes the text in some fashion which you have not
specified in order to send bytes over the wire. Your decode step is
nonsense and the encode step in unspecified.

If you’re going to try to pretend the HTTP transaction is text, you want
to choose a text encoding of the binary data. That invlves two steps:

  • convert the binary data to a text form such as base64, for example
    using the base64.b64encode() function
  • include a Content-Transfer-Encoding header to indicate to the
    receiving client that the data are in base64 form; note that this
    still returns a bytes object, but you can encode that as a str using
    ASCII. (This would let you join everything use as str, it just seems a
    little pointless because request.sendall() is just going to be
    reversing that to send things over the network.)

Cheers,
Cameron Simpson cs@cskk.id.au

I corrected the code converting from string to bytes where it starts to show some result but the image doesn’t render in the browser follows the code and the screen print:

if self.mtd == "GET" and self.pef == "/wsi.png":
    # Define variable of the type string
    self.b_status = bytes(self.pro + "200 OK \n", "utf-8")

    # define string for http header
    self.b_header = bytes(
        "Content-Type: image/png\n"
        "Content-Language: pt-BR\n"
        "Server: Action v0.0.1\n\n",
        "utf-8"
    )

    self.file = open("wsi.png", "rb")
    self.b_img = self.file.read()
    self.file.close()

    self.b_reply = self.b_status + self.b_header + self.b_img

    self.request.sendall(self.b_reply)

I corrected the code converting from string to bytes where it starts to
show some result but the image doesn’t render in the browser follows
the code and the screen print:

if self.mtd == "GET" and self.pef == "/wsi.png":
   # Define variable of the type string
   self.b_status = bytes(self.pro + "200 OK \n", "utf-8")

   # define string for http header
   self.t_header = \
       "Content-Type: image/png\n" \
       "Content-Language: pt-BR\n" \
       "Server: Action v0.0.1\n\n"
   # convert string for bytes
   self.b_header = bytes(self.t_header, "utf-8")

   self.file = open("wsi.png", "rb")
   self.b_img = self.file.read()
   self.file.close()

   self.b_reply = self.b_status + self.b_header + self.b_img

   self.request.sendall(self.b_reply)

This looks a lot better.

This is great screenshot, very informative.

It says that the image is of type “png”, so your header has come
through.

The most obvious concerning thing is that the network trace says that
wsi.png is only 87 bytes long. That seems very small. What size is it on
your local filesystem? Also print out len(self.b_img). And also
len(self.b_reply). Also, does the image itself on your local filesystem
display, eg in some image viewer?

We want to ascertain that (a) the local image file is actually sane and
that (b) you’re sending all the image data to the browser.

Another thing you can use for testing is to collect the image yourself,
not in a browser. For example, on the command line I could use the
wget command:

wget -S -O collected.png http://127.0.0.1:8080/wsi.png

to collect it into the file “collected.png”. Then see if that has the
same content as “wsi.png” etc.

Cheers,
Cameron Simpson cs@cskk.id.au

I got it, the problem was that the image was corrupted and I saved a new one and it worked. Thanks for the help it was of great value because it encouraged me to continue writing an http web server for the python language, very grateful.

view screen :

I got it, the problem was that the image was corrupted and I saved a
new one and it worked.

“Garbage in, garbage out!”

Thanks for the help it was of great value because it encouraged me to
continue writing an http web server for the python language, very
grateful.

I’m glad you perservered to success.

Cheers,
Cameron Simpson cs@cskk.id.au